DEV Community

Cover image for To use React Context for state correctly use it like recoil
Ivan Jeremic
Ivan Jeremic

Posted on • Edited on

To use React Context for state correctly use it like recoil

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
seanblonien profile image
Sean Blonien

@ivanjeremic you never even showed usage of CounterContextProvider, how could this even work?

Collapse
 
blessdarah profile image
Bless Darah Gah

How will you handle nesting of all the independent contexts?

Collapse
 
ivan_jrmc profile image
Ivan Jeremic

Not sure what you mean by nesting, I just wrap the parent where I need it.