Stop reaching for effects
Derive state instead of synchronising it. A mental model that removes most of your effects.
26 articles
Most effects I review are synchronising two pieces of state that should have been one piece of state and a calculation.
The question to ask
Can this value be computed from what I already have during render? If yes, compute it. An effect that sets state from other state is a render that happens twice and a bug that happens eventually.
- Filtering a list is a calculation, not an effect
- Deriving a total is a calculation
- Resetting a form when the identifier changes is a key, not an effect
- Fetching data is genuinely an effect
What is left
Synchronising with something outside the framework: the network, the document title, a subscription, a timer. That is the actual job description.
The three questions
Before writing an effect I ask three things in order, and most effects do not survive the first.
Can I compute this during render?
If the value is a function of other values I already have, compute it. Filtering, sorting, totalling, formatting, deriving a boolean from two other booleans — none of these need an effect, and every one of them I have seen implemented as one.
Can I compute this during the event?
If something should happen because the user did something, do it in the handler. An effect that watches a piece of state so it can react to a click is a click handler with extra steps and a rendering pass in between.
Can I reset this with a key?
If a component needs to forget everything when an identifier changes, give it a key. React will unmount and remount it, all state resets, and you have deleted an effect and a class of bugs at the same time.
What survives
Synchronising with something outside React: a subscription, a timer, the document title, a fetch, an observer. These are genuinely effects, and they all share a shape — set something up, tear it down, and the teardown is the part people skip.
The cost of getting it wrong
An effect that sets state from state produces two renders where one would do. On its own that is nothing. Multiplied through a list of a hundred rows, each with its own derived value, it becomes the reason your interface feels heavy without any single thing being slow.
More importantly it makes the data flow non-obvious. Someone reading the component can no longer tell what the value is by reading upward; they have to simulate an extra pass. That is the real cost, and it does not show up in a profiler.
2 Comments
ThreadedThe key-instead-of-effect trick for resetting forms took me three years to learn. Worth the price of admission.
It is genuinely under-documented. I only found it by reading the source.