State Container Components

Published v1.1.3

State Container Components are React components that hold some state to isolate it, such that rerenders are are avoided in parts of the UI that don’t need to know about said state. It’s a way of colocating state to make your React app faster.

Let’s suppose we have a chart component with two states:

  1. The state of the current pointer coordinates to show a hover line with tooltip
  2. A selection state to select data.

The pointer coordinates change often while the selection does not.

x: 0, y: 284

0100200300400500600700050100150200250300350400A: 284

Here’s what the code looks like roughly.


_32
const allData = { A: ..., B: ... }; // Imagine actual data in here
_32
_32
function Chart() {
_32
const [selected, setSelected] = useState("A");
_32
const [pointerCoords, setPointerCoords] = useState({ x: 0, y: 0 });
_32
_32
const data = allData[selected];
_32
_32
const hoverValue = getHoverValue(data, pointerCoords);
_32
_32
return (
_32
<div>
_32
<Select selected={selected} setSelected={setSelected} />
_32
<svg
_32
onPointerMove={(event) => {
_32
// calculate chart coordinates here
_32
setPointerCoords({ x: point.x, y: point.y });
_32
}}
_32
>
_32
{/* `data` depends on `selected` */}
_32
<Axes data={data} />
_32
<Line data={data} />
_32
<Hover
_32
x={pointerCoords.x}
_32
y={pointerCoords.y}
_32
selected={selected}
_32
hoverValue={hoverValue}
_32
/>
_32
</svg>
_32
</div>
_32
);
_32
}

Half of these component do not need to know about the pointer coords but will still rerender whenever the pointer is moved. This .

Keep state as close to where it’s relevant as possible

Kent C. Dodds - Prop Drilling

Let’s break it down. <Axes /> and <Line /> depend on selected state, so does <Select /> which of course sets it. The function for onPointerMove on the <svg> sets the pointer coords state. At last, <Hover/> needs both states.

It helps to draw a quick diagram for these things, text surely isn’t the best way to explain it. Give it a quick try!

The problem here is that the pointer coords state is placed ’too high’. <Hover/> needs both though, so we can’t move it down, right?

If you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common parent component.

Thinking in React: Identify where your state should

We can in fact move it down! Simply move the components (the subtree) that needs the pointerCoords state into a new component that only holds that state; what I like to call a “State Container Component”.

We can make use of React’s composable nature to nest inside a state container component from the outside using the children prop. Move <svg> and its child <Hover/> to a separate component that holds the pointer coords state and wrap it around the components that don’t need it. Now the other components will not rerender when it is updated, it’s just a matter of good composition!


_42
// ChartSvg is a ’state container component’!
_42
function ChartSvg({ children, data, selected }) {
_42
const [pointerCoords, setPointerCoords] = useState({ x: 0, y: 0 });
_42
_42
const hoverValue = getHoverValue(data, pointerCoords);
_42
_42
return (
_42
<svg
_42
onPointerMove={(event) => {
_42
const point = ... // calculate chart coordinates here
_42
setPointerCoords({ x: point.x, y: point.y });
_42
}}>
_42
{children} {/* <Axes /> and <Line /> will be rendered here */}
_42
<Hover
_42
x={pointerCoords.x}
_42
y={pointerCoords.y}
_42
selected={selected}
_42
hoverValue={hoverValue}
_42
/>
_42
</svg>
_42
);
_42
}
_42
_42
export function Chart() {
_42
const [selected, setSelected] = useState("A");
_42
const data = rawData[selected];
_42
_42
... // chart things here
_42
_42
return (
_42
<div>
_42
<Select selected={selected} setSelected={setSelected} />
_42
<ChartSvg
_42
data={data}
_42
selected={selected}
_42
>
_42
<Axes data={data} />
_42
<Line data={data} />
_42
</ChartSvg>
_42
</div>
_42
);
_42
}

0100200300400500600700050100150200250300350400A: 284

And finally, here's a diagram that summarizes it. The green triangle represent the selected state and the blue square represents the pointerCoords state. I hope it clears things up a little.

Component hierarchy diagram showing before and after using a state container component for the previously given demo code

I recommend you to use the React dev tools with the "Highlight updates when components render." setting on and play around with both charts to see the differences. And finally, always measure before moving code in the name of performance!