I'm trying to build a section of my site that will dynamically pull in contact information from a REST API. At this point I'm using the sample API from https://jsonplaceholder.typicode.com.
I'm trying to use axios, specifically the useAxios and makeUseAxios hooks, to fetch the API, and store it in an app context that I can then use throughout the site, preferably with the useContext hook. Additionally, I'm needing to be able to update the API call based on user interactions, allowing them to select a location, have the API update, and store that in the app's context so that it all updates dynamically.
Essentially, I've gotten a basic useContext scenario working based on this tutorial, but I am struggling with how to store the JSON response in such a way that I can reference it in useContext. Here's the closest thing I've had going so far:
AppContext.tsx
import React, { createContext, ReactNode } from 'react';
import axios from 'axios';
import { makeUseAxios } from 'axios-hooks';
import { useCookie } from 'hooks/use-cookie';
export const AppContext = createContext();
export const DealerContextProvider = ({children}: any) => {
const useAxios = makeUseAxios({
axios: axios.create({ baseURL: 'https://jsonplaceholder.typicode.com/users/' }),
});
const LOCAL_STORAGE_KEY_DEALER = '_selectedDealerInformation';
const [cookie] = useCookie('one-day-location', '1');
const [dealerInfo] = useAxios(`${cookie}`);
return (
<AppContext.Provider value={[dealerInfo]}>
{children}
</AppContext.Provider>
);
};
And my header component, where I'm trying to access it:
import React, { ReactNode, useEffect, useState, useContext } from 'react';
import { AppContext } from 'components/app-context/AppContext';
import Logo from 'assets/svg/logo.svg';
import css from 'classnames';
import { Button } from 'components/button/Button';
import { Link } from 'components/link/Link';
import { NotificationBar } from '../notification-bar/NotificationBar';
import s from './Header.scss';
import { MenuIcon } from 'components/menu-icon/MenuIcon';
import { MainNav } from './navigation/MainNav';
interface HeaderProps {
navigationContent: ReactNode;
}
export const Header = ({ navigationContent }: HeaderProps) => {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
const blogInfo = useContext(AppContext);
const buttonLabel = blogInfo ? `${blogInfo.name}` : 'Find a Dealer';
const buttonLink = blogInfo ? `tel:${blogInfo.name}` : '/find-a-dealer';
useEffect(() => {
const handleScroll = () => {
const isScrolled = window.scrollY > 10;
if (isScrolled !== scrolled) {
setScrolled(!scrolled);
}
};
document.addEventListener('scroll', handleScroll, { passive: true });
return () => {
document.removeEventListener('scroll', handleScroll);
};
}, [scrolled]);
return (
<>
<NotificationBar notificationContent={navigationContent} />
<header
className={scrolled ? css(s.header, s.header__scrolled) : s.header}
data-open={open ? 'true' : ''}
>
<nav className={s.header__navigation}>
<ul className={s.header__container}>
<li className={s.header__logo}>
<Link to="/" className={s.header__link}>
<Logo />
</Link>
</li>
<li className={s.header__primary}>
<MainNav navigationItems={navigationContent} />
</li>
<li className={s.header__utility}>
<Button href={buttonLink}>{buttonLabel}</Button>
</li>
<li className={s.header__burger}>
<MenuIcon onClick={() => setOpen(!open)} />
</li>
</ul>
</nav>
</header>
</>
);
};
What I'm needing is for the button in header__utility to dynamically display the name and phone number of the chosen dealer. I can clarify anything as needed, I'm newish to React and am still learning how to express all that I'm needing.
Thanks!
Alright, I did a ton of digging over the last 24 hours, and I was able to find a solution.
I've got my context, now named ApiContext for clarity.
ApiContext.tsx
import React, { createContext } from 'react';
import axios from 'axios';
import { makeUseAxios } from 'axios-hooks';
import { useCookie } from 'hooks/use-cookie';
const contextObject = {} as any;
export const context = createContext(contextObject);
const useAxios = makeUseAxios({
axios: axios.create({ baseURL: process.env.GATSBY_API_ENDPOINT }),
});
export const ApiContext = ({ children }: any) => {
const [cookie] = useCookie('one-day-location', '1');
const [{ data }] = useAxios(`${cookie}`);
const { Provider } = context;
return <Provider value={data}>{children}</Provider>;
};
Then, to use it across components, I wrap my AppLayout in <ApiContext>:
AppLayout.tsx
import React, { ReactNode } from 'react';
import { ApiContext } from 'contexts/ApiContext';
import { graphql, StaticQuery } from 'gatsby';
import { Devtools } from '../devtools/Devtools';
import { Footer } from '../footer/Footer';
import { Header } from '../header/Header';
import s from './AppLayout.scss';
interface AppLayoutProps {
children: ReactNode;
location: string;
}
const isDev = process.env.NODE_ENV === 'development';
// tslint:disable no-default-export
export default ({ children }: AppLayoutProps) => {
return (
<StaticQuery
query={`${NavQuery}`}
render={(data) => (
<>
<ApiContext>
<Header navigationContent={data.prismic.allNavigations.edges[0].node} />
<div className={s.layout}>
{children}
<Footer navigationItems={data.prismic.allNavigations.edges[0].node} />
{isDev && <Devtools />}
</div>
</ApiContext>
</>
)}
/>
);
};
const NavQuery = graphql`
query NavQuery {
prismic {
allNavigations {
edges {
node {
...NotificationBar
...NavigationItems
...FooterNavigationItems
}
}
}
}
}
`;
And in my Header component I can access the data with the useContext hook:
Header.tsx
import React, { ReactNode, useContext, useEffect, useState } from 'react';
import Logo from 'assets/svg/logo.svg';
import css from 'classnames';
import { Button } from 'components/button/Button';
import { Link } from 'components/link/Link';
import { MenuIcon } from 'components/menu-icon/MenuIcon';
import { context } from 'contexts/ApiContext';
import { NotificationBar } from '../notification-bar/NotificationBar';
import s from './Header.scss';
import { MainNav } from './navigation/MainNav';
interface HeaderProps {
navigationContent: ReactNode;
}
export const Header = ({ navigationContent }: HeaderProps) => {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
const data = useContext(context);
console.log(data);
const buttonLabel = data ? data.name : 'Find a Dealer';
const buttonLink = data ? `tel:${data.phone}` : '/find-a-dealer';
useEffect(() => {
const handleScroll = () => {
const isScrolled = window.scrollY > 10;
if (isScrolled !== scrolled) {
setScrolled(!scrolled);
}
};
document.addEventListener('scroll', handleScroll, { passive: true });
return () => {
document.removeEventListener('scroll', handleScroll);
};
}, [scrolled]);
return (
<>
<NotificationBar notificationContent={navigationContent} />
<header
className={scrolled ? css(s.header, s.header__scrolled) : s.header}
data-open={open ? 'true' : ''}
>
<nav className={s.header__navigation}>
<ul className={s.header__container}>
<li className={s.header__logo}>
<Link to="/" className={s.header__link}>
<Logo />
</Link>
</li>
<li className={s.header__primary}>
<MainNav navigationItems={navigationContent} />
</li>
<li className={s.header__utility}>
<Button href={buttonLink}>{buttonLabel}</Button>
</li>
<li className={s.header__burger}>
<MenuIcon onClick={() => setOpen(!open)} />
</li>
</ul>
</nav>
</header>
</>
);
};
I'm still working on additional optimizations, specifically eliminating a flicker when loading in a page, and also updating the API on a button click, utilizing useCookie, which is a custom hook I've built out. Hopefully this gives some clarity to anyone else searching for this info, it took me hours and hours to determine a solution. Cheers.
Related
I'm supposed to have a modal appear with an image in it. There are next and previous buttons which controls which image you are currently viewing. The modal is rendered in a portal. That in itself is working correctly. However, when I add children, and those childrens are updated, the modal only (not the portal) gets removed from the flow. In the React DevTools, the "isOpen" state of the modal is still set to true. I am using React 17.0.2 with NextJS 12.0.4 and Styled Components 5.3.3.
I have tried:
memoizing my components (as you can see there are some remnants of those trials) but this did not work
extracting the state of the modal to the parent and passing it as props and it didn't work either
I know there must be something wrong that I'm doing here so if you could help me find it that would be much appreciated!
Here is the controller where the modal is rendered:
import { FC, MouseEventHandler, useEffect, useState } from "react";
import { Photo } from "services/Images/Images.interfaces";
import { useGetNextPhoto, useGetPhotos, useGetPreviousPhoto } from "state";
import SlideshowContextProvider from "./Context/SlideshowContext";
import SlideShowModal from "./SlideShowModal";
const SlideshowController: FC = () => {
const photos = useGetPhotos();
const [currentlyViewedPhoto, setCurrentlyViewedPhoto] = useState<Photo | null>(null);
const nextPhoto = useGetNextPhoto(currentlyViewedPhoto?.id);
const previousPhoto = useGetPreviousPhoto(currentlyViewedPhoto?.id);
const onPreviousRequest: MouseEventHandler<HTMLButtonElement> = (event) => {
event.preventDefault();
setCurrentlyViewedPhoto(previousPhoto);
};
const onNextRequest: MouseEventHandler<HTMLButtonElement> = async (event) => {
event.preventDefault();
setCurrentlyViewedPhoto(nextPhoto);
};
useEffect(() => {
setCurrentlyViewedPhoto(photos[0]);
}, [photos]);
return (
<SlideshowContextProvider
currentlyViewing={currentlyViewedPhoto}
onNextSlideRequest={onNextRequest}
onPreviousSlideRequest={onPreviousRequest}
>
<SlideShowModal />
</SlideshowContextProvider>
);
};
export default SlideshowController;
The SlideshowModal:
import { Modal } from "components";
import { FC } from "react";
import SlideshowControlBar from "./SlideshowControlBar";
import SlideshowImage from "./SlideshowImage";
const SlideShowModal: FC = () => {
return (
<Modal uniqueKey="slideshow">
<SlideshowImage />
<SlideshowControlBar />
</Modal>
);
};
export default SlideShowModal;
The modal in itself:
import Portal from "components/Portal/Portal";
import { FC, useEffect, useMemo, useState } from "react";
import { useRegisterModal } from "state";
import styled from "styled-components";
import useWindowScrollLock from "./hook/UseWindowScrollLock";
interface Props {
uniqueKey: string;
isBackgroundOpaque?: boolean;
}
... Styled elements
const Modal: FC<Props> = ({ uniqueKey, isBackgroundOpaque = true, children }) => {
const [isOpen, setIsOpen] = useState(false);
const open = () => setIsOpen(true);
const close = () => setIsOpen(false);
const register = useRegisterModal(uniqueKey);
const isModalOpen = useMemo(() => isOpen, [isOpen]);
useEffect(() => {
register({ open, close });
}, [register]);
useWindowScrollLock(isModalOpen);
return isModalOpen ? (
<Portal>
<Container>
<InnerModal>
<Close onClick={close}>X</Close>
{children}
</InnerModal>
</Container>
<Background onClick={close} opaque={isBackgroundOpaque} />
</Portal>
) : null;
};
export default Modal;
I´m new to NextJS and React at all so I ask for your forgiveness.
I want to know how to pass an users written text from an input field (inside of Header) into the getStaticProbs function of a specific page via the react context api.
I tried the following source but it doesn`t work - it throws out an error that my way to build leads to an invalid hook call.
Here is my context source:
import React, { createContext, useState } from 'react';
export const SearchContext = createContext();
export const SearchProvider = ({ children }) => {
const [keyword, setKeyword] = useState('');
return (
<SearchContext.Provider
value={{
keyword,
setKeyword,
}}
>
{children}
</SearchContext.Provider>
);
};
to fetch the written string of SearchBar.js:
import React, { useContext, useState } from 'react';
import { useRouter } from 'next/router';
import Image from 'next/image';
import loupe from '../public/images/loupe.png';
import { SearchContext } from '../lib/searchCtx';
const SearchBar = () => {
const search = useContext(SearchContext);
const router = useRouter();
const submitAction = (e) => {
e.preventDefault();
router.push(`/searchResults`);
};
return (
<div className={styles.searchBar}>
<input
type='text'
placeholder='Suche...'
onChange={(e) => search.setKeyword(e.target.value)}
/>
<button className={styles.searchBtn} type='submit' onClick={submitAction}>
<Image src={loupe} alt='' />
</button>
</div>
);
};
export default SearchBar;
and pass it over _app.js:
import Header from '../components/Header';
import Footer from '../components/Footer';
import { SearchProvider } from '../lib/searchCtx';
function MyApp({ Component, pageProps }) {
return (
<>
<SearchProvider>
<Header />
<Component {...pageProps} />
</SearchProvider>
<Footer />
</>
);
}
}
export default MyApp;
to get the value into getStaticProbs of searchResults.js:
import { useEffect, useState, useContext } from 'react';
import { fetchData } from '../lib/utils';
import styles from '../styles/Playlist.module.scss';
import Image from 'next/image';
import { SearchContext } from '../lib/searchCtx';
export default function SearchResults({ videos }) {
console.log(videos);
const sortedVids = videos
.sort((a, b) =>
Number(
new Date(b.snippet.videoPublishedAt) -
Number(new Date(a.snippet.videoPublishedAt))
)
)
return (
<>
<div className={`${styles.playlist_container} ${styles.search}`}>
<h1>Search results</h1>
{sortedVids
.map((vid, id) => {
return (
<div className={styles.clip_container}>
<Image
className={styles.thumbnails}
src={vid.snippet.thumbnails.medium.url}
layout='fill'
objectFit='cover'
alt={vid.snippet.title}
/>
</div>
<div className={styles.details_container}>
<h3>{vid.snippet.title}</h3>
</div>
);
})}
</div>
</>
);
}
export async function getStaticProps() {
const search = useContext(SearchContext);
const { YOUTUBE_KEY } = process.env;
const uploadsURL = `https://youtube.googleapis.com/youtube/v3/search?part=snippet&channelId=UCbqKKcML7P4b4BDhaqdh_DA&maxResults=50&key=${YOUTUBE_KEY}&q=${search.keyword}`;
async function getData() {
const uploadsData = fetchData(uploadsURL);
return {
videos: await uploadsData,
};
}
const { videos } = await getData();
return {
revalidate: 86400,
props: {
videos: videos.items,
},
};
}
Would you help me by 1) telling me the main failure I did and 2) providing me a working source?
How can I achieve it to get the keyword from SearchContext into the uploadsURL (inside of getStaticProbs) or isn`t it possible?
Thanks in advance!!
You can create a dynamic pages under your page folder one called say index.js and one called [slug].js (all under one folder) In the index page you can have your normal search input, when the users submit the query you can do
<a
onClick={() =>
router
.push(`/movies/${search.keyword}`)
.then(() => window.scrollTo(0, 0))}>
search
</a>
and in your [slug].js page you can retrieve that information like so
export async function getServerSideProps(pageContext) {
const pageQuery = pageContext.query.slug;
const apiCall= await fetch(
``https://youtube.googleapis.com/youtube/v3/search?part=snippet&channelId=UCbqKKcML7P4b4BDhaqdh_DA&maxResults=50&key=${YOUTUBE_KEY}&q=${pageQuery}`
);
const results = await apiCall.json();
return {
props: {
data: results,
},
};
}
I don't know if this will work for you but is a solution
I'm doing a course of React Js, in the terminal not have errors but i have a error in the console of the navigator.
This is the error:
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of ToggleLikeMutation.
at ToggleLikeMutation (webpack://curso-platzi-react-avanzado/./src/container/ToggleLikeMutation.js?:16:23)
at article
at styled.article (webpack://curso-platzi-react-avanzado/./node_modules/styled-components/dist/styled-components.browser.esm.js?:29:19307)
at PhotoCard (webpack://curso-platzi-react-avanzado/./src/components/PhotoCard/index.js?:31:17)
at ul
at ListOfPhotoCards (webpack://curso-platzi-react-avanzado/./src/components/ListOfPhotoCards/index.js?:22:25)
at div
at App
at ApolloProvider (webpack://curso-platzi-react-avanzado/./node_modules/#apollo/client/react/context/ApolloProvider.js?:12:21)
Here's my documents:
FavButton.js
import React from 'react'
import { MdFavoriteBorder, MdFavorite } from 'react-icons/md'
import { Button } from './styles'
export const FavButton = ({ liked, likes, onClick }) => {
const Icon = liked ? MdFavorite : MdFavoriteBorder
return (
<Button onClick={onClick}>
<Icon size='32px' /> {likes} likes!
</Button>
)
}
ToggleLikeMutation.js
import React from 'react'
import { Mutation, gql } from '#apollo/client'
const LIKE_PHOTO = gql`
mutation likeAnonymusPhoto($input: LikePhoto!) {
likeAnonymousPhoto(input: $input) {
id,
liked,
likes
}
}
`
export const ToggleLikeMutation = ({ children }) => {
return (
<Mutation mutation={LIKE_PHOTO}>
{children}
</Mutation>
)
}
PhotoCard.js
import React, { Fragment } from 'react'
import { Article, ImgWrapper, Img } from './styles'
import { useLocalStorage } from '../../hooks/useLocalStorage'
import { useNearScreen } from '../../hooks/useNearScreen'
import { FavButton } from '../FavButton/index.js'
import { ToggleLikeMutation } from '../../container/ToggleLikeMutation'
const DEFAULT_IMAGE = 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60'
export const PhotoCard = ({
id, likes = 0,
src = DEFAULT_IMAGE
}) => {
const [show, element] = useNearScreen()
const key = `like-${id}`
const [liked, setLiked] = useLocalStorage(key, false)
return (
<Article ref={element}>
{
show &&
<>
<a href={`/?detail=${id}`}>
<ImgWrapper>
<Img src={src} />
</ImgWrapper>
</a>
<ToggleLikeMutation>
{
(toggleLike) => {
const handleFavClick = () => {
!liked && toggleLike({
variables: {
input: { id }
}
})
setLiked(!liked)
}
return (
<FavButton
liked={liked} likes={likes} onClick={handleFavClick}
/>
)
}
}
</ToggleLikeMutation>
</>
}
</Article>
)
}
App.js
import React, { Fragment } from 'react'
import { ListOfCategories } from './components/ListOfCategories'
import { GlobalStyle } from './styles/GlobalStyles'
import { ListOfPhotoCards } from './components/ListOfPhotoCards'
import { Logo } from './components/Logo'
import { PhotoCardWithQuery } from './container/PhotoCardWithQuery'
export const App = () => {
const urlParams = new window.URLSearchParams(window.location.search)
const detailId = urlParams.get('detail')
return (
<div>
<GlobalStyle />
<Logo />
{
detailId
? <PhotoCardWithQuery id={detailId} />
: <>
<ListOfCategories />
<ListOfPhotoCards categoryId={1} />
</>
}
</div>
)
}
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { ApolloClient, HttpLink, InMemoryCache } from 'apollo-boost'
import { ApolloProvider } from '#apollo/client'
import { App } from './App'
const cache = new InMemoryCache()
const link = new HttpLink({
uri: 'https://petgram-jv-2011.vercel.app/graphql'
})
const client = new ApolloClient({
cache,
link
})
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('app'))
I've got component that displays contact information from a dealer as chosen by the user. To be more specific, a user selects their location, setting a cookie which then is used to define the API call. I pull in the contact information of the dealer in that location using Axios, store it in a context, and then display the information as necessary through several components: the header, a "current location" component etc. However, I'm having an issue with the content flickering each time the page is refreshed.
I've tried storing the JSON response in local storage, but, for a brief moment on page load, it shows as undefined, making the flicker continue. Obviously, I'm needing to eliminate that so that the data persists.
I've got it working via ApiContext, and I'm displaying the data in my Header component. Below is the code for both:
ApiContext.tsx
import React, { createContext, useEffect, useState } from 'react';
import axios from 'axios';
const contextObject = {} as any;
export const context = createContext(contextObject);
export const ApiContext = ({ children }: any) => {
const [selectedDealer, setselectedDealer] = useState(`1`);
useEffect(() => {
axios
.get(`${process.env.GATSBY_API_ENDPOINT}/${selectedDealer}`)
.then((response) => setselectedDealer(response.data));
}, [selectedDealer]);
const changeDealer = (id: any) => {
setselectedDealer(id);
};
const { Provider } = context;
return (
<Provider value={{ data: selectedDealer, changeDealer: changeDealer }}>
{children}
</Provider>
);
};
Header.tsx
import React, { ReactNode, useContext, useEffect, useState } from 'react';
import Logo from 'assets/svg/logo.svg';
import css from 'classnames';
import { Button } from 'components/button/Button';
import { Link } from 'components/link/Link';
import { MenuIcon } from 'components/menu-icon/MenuIcon';
import { context } from 'contexts/ApiContext';
import { NotificationBar } from '../notification-bar/NotificationBar';
import s from './Header.scss';
import { MainNav } from './navigation/MainNav';
interface HeaderProps {
navigationContent: ReactNode;
}
export const Header = ({ navigationContent }: HeaderProps) => {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
const data = useContext(context);
const buttonLabel = data ? data.name : 'Find a Dealer';
const buttonLink = data ? `tel:${data.phone}` : '/find-a-dealer';
useEffect(() => {
const handleScroll = () => {
const isScrolled = window.scrollY > 10;
if (isScrolled !== scrolled) {
setScrolled(!scrolled);
}
};
document.addEventListener('scroll', handleScroll, { passive: true });
return () => {
document.removeEventListener('scroll', handleScroll);
};
}, [scrolled]);
return (
<>
<NotificationBar notificationContent={navigationContent} />
<header className={scrolled ? css(s.header, s.header__scrolled) : s.header}>
<nav className={s.header__navigation}>
<ul className={s.header__container}>
<li className={s.header__logo}>
<Link to="/" className={s.header__link}>
<Logo />
</Link>
</li>
<li className={s.header__primary}>
<MainNav navigationItems={navigationContent} />
</li>
<li className={s.header__utility}>
<Button href={buttonLink}>{buttonLabel}</Button>
</li>
<li className={s.header__icon}>
<MenuIcon onClick={() => setOpen(!open)} />
</li>
</ul>
</nav>
</header>
</>
);
};
I would assume that this is because the API call is being triggered each time the page is refreshed, so I wonder if there's any way to persist the data in a more efficient way?
Thanks in advance!
Your ApiContext.tsxcould persist the data in localStorage is such a way:
import React, { createContext } from 'react';
import axios from 'axios';
import { makeUseAxios } from 'axios-hooks';
import { useCookie } from 'hooks/use-cookie';
const contextObject = {} as any;
export const context = createContext(contextObject);
const useAxios = makeUseAxios({
axios: axios.create({ baseURL: process.env.GATSBY_API_ENDPOINT }),
});
const loadData = (cookie) => {
const stored = localStorage.getItem("data");
const parsed = JSON.parse(stored);
// You can also store a lastSync timestamp along with the data, so that you can refresh them if necessary
if (parsed) return parsed;
const [{data}] = useAxios(`${cookie}`);
if (!isEqual(parsed, data)) {
localStorage.setItem('data', JSON.stringify(data));
}
return data
}
export const ApiContext = ({ children }: any) => {
const [cookie] = useCookie('one-day-location', '1');
const [{ data }] = loadData(cookie);
const { Provider } = context;
return <Provider value={data}>{children}</Provider>;
};
The above implementation will only fetch the data once, so remember to refresh them at some point inside your code and update the localStorage item, or use a timestamp to compare and force the api call as commented in my code.
Keep in mind that even this implementation may take a fraction of a second to be completed, so I would suggest to always use loaders/spinners/skeletons while your application is fetching the required data.
I got this worked out, using a hook that persists my state, storing it in a localStorage item.
usePersistState.ts
import { useEffect, useState } from 'react';
export const usePersistState = (key: string, defaultValue: string) => {
const [value, setValue] = useState(() => {
if (typeof window !== 'undefined') {
const stickyValue = window.localStorage.getItem(key);
return stickyValue !== null ? JSON.parse(stickyValue) : defaultValue;
}
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
Then, in ApiContext, I set my default state, but when that state changes, it updates and persists the state. Here's my context component now:
ApiContext.tsx
import React, { createContext, useEffect } from 'react';
import { usePersistState } from 'hooks/use-persist-state';
import axios from 'axios';
const contextObject = {} as any;
export const context = createContext(contextObject);
const LOCAL_STORAGE_KEY_SELECTED_DEALER = 'selectedDealerInformation';
export const ApiContext = ({ children }: any) => {
const [selectedDealer, setselectedDealer] = usePersistState(LOCAL_STORAGE_KEY_SELECTED_DEALER, '1');
useEffect(() => {
axios
.get(`${process.env.GATSBY_API_ENDPOINT}/${selectedDealer}`)
.then((response) => setselectedDealer(response.data));
}, [selectedDealer]);
const changeDealer = (id: any) => {
setselectedDealer(id);
};
localStorage.setItem(LOCAL_STORAGE_KEY_SELECTED_DEALER, JSON.stringify(selectedDealer));
const { Provider } = context;
return (
<Provider value={{ data: selectedDealer, changeDealer: changeDealer }}>{children}</Provider>
);
};
I have tried some solutions that came by, on this link particularily...
I tried changing value inside my TodosContext.js file.. which didn't work, too..
One more thing that I have tried is to call useContext() function from another component, that didn't work neither..
Here's my code.
App.js:
import React, { useState, useContext } from 'react';
import TodoList from './components/TodoList';
import NewTodo from './components/NewTodo';
import { TodosProvider, TodosContext } from './components/contextapi/TodosContext';
function App() {
const [input, setInput] = useState('');
const [todos, setTodos] = useContext(TodosContext);
const _handleInput = (e) => {
setInput(e.target.value)
}
const _todoAdd = (e) => {
if (e.key === 'Enter') {
setTodos(
[...todos, { content: input, id: Date.now(), completed: false }]
)
setInput('')
}
}
const _todoRemove = (id) => {
const newTodos = todos.filter(todo => todo.id !== id)
setTodos(newTodos)
}
return (
<div>
<header>
<h3>To-Do Manager | Context API</h3>
</header>
<TodosProvider>
<NewTodo newTodo={_todoAdd} handleInput={_handleInput} newVal={input} />
<TodoList todos={todos} />
</TodosProvider>
</div>
);
}
export default App;
TodosContext.js:
import React, { useState, createContext } from 'react';
export const TodosContext = createContext()
export const TodosProvider = ({ children }) => {
const [todos, setTodos] = useState([]);
return (
<TodosContext.Provider value={[todos, setTodos]}>{children}</TodosContext.Provider>
)
}
TodoList.js:
import React, { useContext } from 'react';
import Todo from './Todo';
import RemoveTodoFromList from './RemoveTodoFromList';
import { TodosContext } from './contextapi/TodosContext'
function TodoList() {
const [todos, setTodos] = useContext(TodosContext);
return (
<div>
{todos.map(todo => (
<div>
<Todo key={todo.id} todo={todo} />
</div>
))}
</div>
)
}
export default TodoList
I'm really struggling with this one, I spent whole day figuring out what went wrong.. Thanks!
We fixed it inside the comments.
createContext needs an object as parameter that defines your context.
In your case it should be export const TodosContext = createContext([[],() => {}]).
So the application knows the first element of the tuple is an array and so iterable.