To manage state in React, we can use 3rd party libraries such as Redux, Mobx, Zustand, etc. These libraries provide not just a state management function, but also other optimized solutions such as caching, etc. These libraries allow avoiding a specific problem that we call Prop drilling
.
As we all know, React also provides such a solution called Context APIs
.
This article shares an effective way of using Context APIs
in your React project. Please note that this article doesn't explain the detail of how to use Context APIs
. To understand the basic concept, please refer to the React official document createContext
Suggested Folder Structure
src
├── contexts
│ └── user
│ ├── userContext.tsx
│ └── userProvider.tsx
├── ...
userContext.tsx
import { createContext } from 'react';
interface IUserContext {
name: string;
setName: React.Dispatch<React.SetStateAction<string>>;
}
const UserContext =
createContext<IPersonContext>({} as IPersonContext);
export default PersonContext;
In the userContext.tsx
, we can use createContext
to create and define the context value that can be shared to child components.
As we all know, to use the context's value, we need to use provider
and consumer
something like as follows.
return <ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
However, it is tedious that we have to pass the value to the Provider
whenever we use it and define the Consumer
to get the value. This looks ugly and hard to read!
To make code clean and neat (also take a single responsibility), we can create a Higher Order Component with Context Provider
.
userProvider.tsx
import { ReactElement, useMemo, useState } from 'react';
import UserContext from './userContext';
type UserProviderProps = {
children: ReactElement;
};
export default function UserProvider({ children }: UserProviderProps) {
const [name, setName] = useState<string>('');
const value = useMemo(
() => ({
name,
setName,
}),
[name]
);
return <PersonContext.Provider value={value}>{children}</PersonContext.Provider>;
}
As described in the code above, UserProvider
includes the context value, and the value includes multiple props
within a single object. To avoid unnecessary rerendering occurs, we wrap the value with useMemo
. (Please note that the dependency for the memoization
is name
only because setName
won't be affected by other changes.
How To Use
We need to define the context boundary by using UserProvider
.
import UserProvider from './userProvider';
import Account from './Account';
export default function App() {
return <UserProvider>
<Account />
</UserProvider>;
}
In the Account component, we can use useContext
to get the context value
.
Account.tsx
import { useContext } from 'react';
import UserContext from './userContext";
export default function Account () {
const {name, setName} = useContext(UserContext);
return <div>
<div>
Name: {name}
<div>
<input type="text"
onChange={(e) => {
setName(e.target.value);
}
}/>
</div>
}
Now, we can use this pattern to create multiple contexts and use them in any child components whenever they are needed!
Top comments (0)