React doesn't need a state management tool, I said. Then I built one

granat_stepan1 pts0 comments

React doesn't need a state management tool, I said. Then I built one.skip to contentA while back I wrote an article with a title I stand behind to this day: React doesn&rsquo;t need a state management tool. There&rsquo;s already a library for everything, and each new one just adds another decision to an exhausting pile.<br>So naturally, the next thing I did was build a React library. Before you close the tab — give me a chance. I think I have a decent excuse, and it doesn&rsquo;t actually contradict a word of that article.<br>The other 20%<br>I&rsquo;m a strong advocate of local state with specialized tools for specific tasks — react-query or SWR for data fetching, formik or react-hook-form for forms, and so on. For maybe 80% of the cases this works perfectly: simple forms, views with a few buttons, no problem.<br>But honestly, the remaining 20% often get ugly. In some parts of the app a centralized state simply is the better way. You might have some really complicated form, editor or some complex piece where everything is somehow connected to everything and you need to control it from one place.<br>Existing solutions<br>The usual advice is to reach for a lightweight state manager like zustand — a mini redux store you can use anywhere. But I think that misses the point. formik, react-query, zustand — they&rsquo;re all sources of state, and I usually already have several: some data is fetched, some lives in formik, some is my own custom state. I don&rsquo;t want to add another source; I want to combine the ones I have into a single piece, and let components use it regardless of where it came from.<br>With zustand you get there by copying everything into the store — but then it&rsquo;s no longer the single source of truth, and since it lives outside React&rsquo;s lifecycle, it&rsquo;s easy to forget to clean up on unmount. Jotai gets much closer: with derived atoms and atomWithQuery you really can combine fetched and local state. But notice what it took — everything had to become an atom. Jotai is still a state source, a new primitive you build on top of. What I wanted was different: not another place to keep state, but a way to combine the state I already have.<br>What is actually missing?<br>I used to reach for React Context, and conceptually it&rsquo;s exactly right: it gives that state a single home. You find a common ancestor, move the state up there — the same useState, useQuery and mutating functions you&rsquo;d write in a local component, only now combined in one place — and pass the value and actions down. That&rsquo;s the whole point: one &ldquo;controller&rdquo; component that owns the combined state and resets automatically when it unmounts.<br>There&rsquo;s just one problem, and it&rsquo;s a big one: performance. Context forces every subscriber to re-render on every change — one keystroke can re-render half your app. That&rsquo;s why sharing frequently-changing state through context is considered bad practice.<br>Meet react-arven<br>Imagine we could keep everything good about context and lose the re-renders. That&rsquo;s exactly what react-arven is for. The name is a bit mystical, but the idea is simple — and it falls out of three specific problems with Context, so let me walk through them.<br>1. The ability to select data<br>The most obvious issue of the React Context API is the fact that it can only provide a single value, and whenever that value changes, it forces all subscribers to re-render.<br>Libraries like redux or zustand allow us to select only the part we want, and then our component re-renders only if the required piece of the state changed.<br>The trick to fix this is actually quite simple: we create a ref and hide the state in its value.current mutable property. Then we can use a well-known mechanism from redux — selectors.<br>const items = useContextSelector(Context, c => c.items)

Simple, powerful.<br>To be fair, none of this is new. dai-shi&rsquo;s use-context-selector popularized exactly this pattern, and since React 18 the primitive ships built-in — useSyncExternalStore lets a component subscribe to an external store through a selector.<br>2. Preventing parent causing re-render<br>This is a hidden foot-gun, let me show an example.<br>const Context = React.createContext(...)

function Parent() {<br>const [count, setCount] = useState(0)

return (<br>Context.Provider value={{count, setCount}}><br>TreeOfChildren /><br>Context.Provider>

It&rsquo;s easy to use context like this, but there is a huge performance problem. Because the component sits above the whole subtree, whenever it re-renders all of its children re-render as well (unless they use React.memo). Even components that don&rsquo;t use the context at all get re-rendered — it&rsquo;s not actually the context causing this, it&rsquo;s the parent-child structure.<br>We need to do a subtle change, to prevent this:<br>const Context = React.createContext(...)

function CountProvider ({ children }) {<br>const [count, setCount] = useState(0)<br>return (<br>Context.Provider value={{count,...

rsquo state react context everything value

Related Articles