React State Co-Location
We all know that React has re-rendering performance challenges. This is also inherent in how React works: when a component’s state or props change, a re-render occurs. However, it often becomes a performance bottleneck when developers do not pay attention to how they write React code, or when the codebase starts to scale.
Let’s look at the example
import { useState } from 'react'
import Counter from '../Counter'
function Page() {
const [counter1, setCounter1] = useState(0)
const [counter2, setCounter2] = useState(0)
return (
<>
<label>Counter 1</label>
<Counter
value={counter1}
increase={() => setCounter1(prev = prev + 1)}
decrease={() => setCounter1(prev = prev - 1)}
/>
<label>Counter 2</label>
<Counter
value={counter2}
increase={() => setCounter2(prev = prev + 1)}
decrease={() => setCounter2(prev = prev - 1)}
/>
</>
)
}
The page contains two
For a simple example like this, that behavior is usually acceptable. But in real-world applications, pages often contain many more states and much more complex component trees. As the app grows, these unnecessary re-renders can turn into real performance problems, because every update causes large portions of the page’s component tree to re-render—even when most of it hasn’t actually changed.
You might start trying to optimize it with memo, useCallback, and useMemo, but before you know it, you’ve ended up writing more code that’s harder to read and maintain.
State Co-Location
State co-location is a simple technique that helps improve performance without writing any explicit optimization code. The idea is to place state as close as possible to where it’s actually used. Let’s see the code
import { useState } from 'react'
import Counter from '../Counter'
function Page() {
return (
<>
<Counter1 />
<Counter2 />
</>
)
}
function Counter1() {
const [counter1, setCounter1] = useState(0)
return (
<>
<label>Counter 1</label>
<Counter
value={counter1}
increase={() => setCounter1(prev = prev + 1)}
decrease={() => setCounter1(prev = prev - 1)}
/>
</>
)
}
function Counter2() {
const [counter2, setCounter2] = useState(0)
return (
<>
<label>Counter 2</label>
<Counter
value={counter2}
increase={() => setCounter2(prev = prev + 1)}
decrease={() => setCounter2(prev = prev - 1)}
/>
</>
)
}
In this example, we can extract the relevant state and logic for each counter into its own component—