One of the biggest problems in managing state with Context is that react re-renders all the children if a value in the provider changes, So having multiple pieces of state that have nothing to do with one another will make your applications do unnecessary re-renders all the time and this is not manageable stop this!
Imagine having a counter state and a modal state and both are provided to the App via the same Context, that means when you open/close the modal all components of the counter will rerender to.
So how to solve this problem? For people who are familiar with Recoil js, they know that the so-called atoms
are only one piece of state and not a store for having all kinds of state in it, they hold really only one piece. So let's do the same in Context, we will create for each state of our application a separate Context file that will hold only one piece of state maximum, Our Provider will provide only the state
and the setter
for this one piece of state.
Here an example with counter
& modal
state
/contexts/CounterContext.js
export const CounterContext = createContext();
export function CounterContextProvider({ children }) {
const [count, setCount] = useState(0);
return (
<CounterContext.Provider value={[count, setCount]}>
{children}
</CounterContext.Provider>
);
}
and the modal in a separate file.
/contexts/ModalContext.js
export const ModalContext = createContext();
export function ModalContextProvider({ children }) {
const [open, setOpen] = useState(false);
return (
<ModalContext.Provider value={[open, setOpen]}>
{children}
</ModalContext.Provider>
);
}
I recommend using a folder "contexts" that holds all your state if you are used to "stores" look at you contexts folder as store :)
Now you use the state where you need it as you develop, important here is never wrap the whole App in the providers, if a button in the Header component needs the counter state only wrap the parts one level above in the provider or even more cleaner create a wapper folder and create a wrapper for each component that needs state, this way only the parts re-render that need to change.
/wrappers/CounterButtonWrapper.js
function CounterButton() {
const [count, setCount] = useContext(CounterContext);
const increment = () => {
setCount((prevState) => {
return prevState + 1
})
}
return (
<button onClick={increment}>Increment</Button>
);
}
// use this in your Header
export default function CounterButtonWrapper() {
return (
<CounterContext.Provider>
<CounterButton />
</CounterContext.Provider>
);
}
Of course, it is more boilerplate than recoil but not everyone wants to use libraries and if you really want to manage client state with Context then this method with separate contexts for each piece of state and wrappers will scale and is the best way if you ask me.
Top comments (3)
@ivanjeremic you never even showed usage of
CounterContextProvider
, how could this even work?How will you handle nesting of all the independent contexts?
Not sure what you mean by nesting, I just wrap the parent where I need it.