Context, state and rerenders
Tags: react, context, state
Published: 2020-08-02 11:36:18

Hey guys! I have a topic I'd like to ramble about and also know your opinions on.

We all know `Context`. An we know it can (but sometimes shouldn't) be used to provide _some sort_ of global state. But there's normally a problem: controlling rerenders. Let's dig a bit deeper.

# How to use `Context` for global state

Again: everyone probably knows this, but `Context` just provides a value to every component below the `Provider`. So we could just do this:

```typescript
...
<Context.Provider value={0}><Chidlren /></Context.Provider>
...
```

Now, of course we want to make this value dynamic. Having a fully static value as the `Context`'s value makes it more of a config value that app state.
So, to make it dynamic, we just have to assign it to a variable, right?

```typescript
const Wrapper: FC = () => {
    let counter = 0
    const inc = () => counter++
    return <Context.Provider value={counter}><Chidlren /></Context.Provider>
}
```

But you may have noticed that `counter` is not state. So changing `counter` (by using `inc`) won't cause a render on `Wrapper` and, therefore, on `Children`.

"Easy to solve, just use state!" Fair enough, let's try that:


```typescript
const Wrapper: FC = () => {
    const [counter, setCounter] = useState(0)
    const inc = useCallback(() => setCounter(c => c + 1), [setCounter]) // Using useCallback is not necessary
    return <Context.Provider value={counter}><Chidlren /></Context.Provider>
```

Now, if we call `inc`, the `Wrapper`'s state will change and it will render, passing a new value to the `Context.Provider` and the `Children` to also render with this new value.

## The new problem

But wait: aren't `Provider`s supposed to be relatively high up in the tree? And isn't updating their state gonna cause _everything_ below them to render? Well, yes. And we don't want that.

Say you have this structure:

```typescript
<Wrapper />
// which renders
<Context.Provider /> // provides counter
// which renders
<ChildDeep1 />
// which renders
<ChildDeep2 />
// which renders
<ChildDeep3 /> // only this one needs counter
```

_Wow bro, that's deep._ I know right? Anyway, if we only need `counter`on `ChildDeep3`, this is cause (potentially many) unnecessary rerenders along the tree.

## The solution

The solution to this problem is two-fold:
    1) maybe it's better to just optimize the renders and let React render the whole thing. If the tree is not too big and making these optimizations is easy, try it. Else,
    2) `useMemo()` to the rescue! Honestly I took way to long to figure this out, but wrapping the first children in a `useMemo()` prevents it from rendering, but ***doesn't*** prevent deeply nested children to update if they consume the `Context`'s value! This is awsome. Now you can have this:

```typescript
<Wrapper />
// which renders
<Context.Provider /> // provides counter
// which renders
const child = useMemo(() => <ChildDeep1 />, [])
{child}
// ChildDeep1 renders
<ChildDeep2 />
// which renders
<ChildDeep3 /> // only this one needs counter
```

### Small caveat

If you want to pass props directly to the first child of the `Provider`, you just need to pass them normally (inside the `useMemo()`) and add them to its dependencies, like so:

```typescript
const child = useMemo(() => <ChildDeep1 prop={prop} />, [someValue])
```

Now if `prop` changes, `ChildDeep1` rerenders (and everything below it) as normal.

You can check out a working demo here: https://codesandbox.io/s/intelligent-nobel-jcxeq?file=/src/App.tsx

# Conclusion

This pattern should be used in other situations, even if they don't include `Context`, because it allows to very precisely control how components rerender. In short: hooks are great.