A useState and Context API pattern that avoids redundant renders

Alasdair McLeay
2 min readMar 12, 2020

--

My initial attempt at a basic pattern that combines React Context with useState was as follows:

codesandbox

But I soon noticed this caused renders that I wasn’t expecting (compared to a more traditional Redux and class based components approach):

Creating new objects

On a side note, the above code creates a new object every time the state changes:

const [value, set] = useState("initial");
return <MyContext.Provider value={{ value, set }} {...props} />;

A better approach for this would be as follows:

const valueState = useState("initial");
return <MyContext.Provider value={valueState} {...props} />;

Split contexts that don’t change together

In the previous example, the SetValue component renders because it is using the same context provider as the changing value. But the set function doesn’t change so there should be no need to render the button.

Option 1 (Preferred): Split contexts that don’t change together
Option 2: Split your component in two, put memo in between
Option 3: One component with useMemo inside
https://github.com/facebook/react/issues/15156#issuecomment-474590693

Dan Abramov recommends splitting contexts that don’t change together. In our simple example, set and value don’t change together.

Following this advice results in the following:

codesandbox

This prevents SetValue from rendering when value changes, but there are still renders that aren’t needed:

In this example, the Provider component renders when the state changes, which causes SetContext to render.

We can swap the order of SetContext and ValueContext but still have a similar issue:

codesandbox

Both context providers still render:

Provider renders child

In this basic example, this may be a micro optimisation, but when trying to establish a useState/Context API pattern to use on a larger scale I would prefer to not be rendering the SetContext.Provider if not needed.

This tip results in the following:

codesandbox

And we now have a pattern that results in only rendering what is needed:

--

--