I am trying to create a parent/overarching context
. The purpose of the context is to store UserInfo
which should then be available to all children.
UserInfo
is set by getting config from an API.
Context.ts:
import type { User } from 'my-lib';
import { Dispatch, SetStateAction } from 'react';
export const UserContextProps = {
setUser?: Dispatch<SetStateAction<User>>,
userConfig?: User
}
export const UserContext = createContext<UserContextProps>({});
UserProvider.tsx
import { UserContext } from './Context'
const UserProvider = ({children}) =>{
const [data, setUser] = useState<User>({});
useEffect(() =>{
fetchData().then(res => setUser(res.data))
},[]);
return(
<UserContext.Provider value={{setUser: setUser, userConfig: data}}>
{Object.keys(user).length > 0 ? (
<>{children}</>
) : (
<div>Loading</div>
)}
</UserContext.Provider>
)
}
Our app is made up of many small sites like cart.html
, description.html
, profile.html
each of these has its own component. I am trying to wrap each of these with UserProvider
so the children:
1. Render after userConfig is set
2. Have access to userConfig
Cart.tsx
import { UserProvider } from 'UserProvider';
import React, { useContext } from 'react';
import { UserContext } from './Context'
export const UserCartApp = () => {
const { userConfig } = useContext(UserContext);
return (
<UserProvider>
<Header userConfig={userConfig} />
<SelectedProducts userConfig={userConfig} />
<SubTotal userConfig={userConfig} />
<Footer />
</UserProvider>
)
}
Inside i have:
Header.tsx
export const Header = ({userConfig}) => {
console.log('header config', userConfig);
....
....
The above console.log
of userConfig is always {}
Components not wrapped in the Context provider will receive the default value.
export const UserContext = createContext<UserContextProps>({});
The UserContext
has a default value of {}
.
Since UserCartApp
is the component rendering the UserProvider
component providing the UserContext
it can't itself access the provided value.
export const UserCartApp = () => {
const { userConfig } = useContext(UserContext); // <-- receives default {} value
return (
<UserProvider> // <-- Provides the context
<Header userConfig={userConfig} />
<SelectedProducts userConfig={userConfig} />
<SubTotal userConfig={userConfig} />
<Footer />
</UserProvider>
);
};
Lift the UserProvider
component higher in the ReactTree such that it can provide the UserContext
value to UserCartApp
.
Example:
<UserProvider>
<UserCartApp />
</UserProvider>
export const UserCartApp = () => {
const { userConfig } = useContext(UserContext); // <-- receives { setUser: setUser, userConfig: data } value
return (
<>
<Header userConfig={userConfig} />
<SelectedProducts userConfig={userConfig} />
<SubTotal userConfig={userConfig} />
<Footer />
</>
);
};
One of the problems the React Context API solves for though is props drilling. It doesn't make sense to access the context value in the parent component and then pass it down as props to each child component. An improvement here would be to simply have each child use the const { userConfig } = useContext(UserContext);
.
Example:
const Header = () => {
const { userConfig } = useContext(UserContext);
...
};
const SelectedProducts = () => {
const { userConfig } = useContext(UserContext);
...
};
const SubTotal = () => {
const { userConfig } = useContext(UserContext);
...
};
export const UserCartApp = () => {
return (
<>
<Header />
<SelectedProducts />
<SubTotal />
<Footer />
</>
);
};