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)