Do you know useEffect
?
useEffect(() => {
console.log(value)
}, [value])
This
effect
runs after the firstrender
and whenevervalue
changes.
I bet you've probably learned it that way.
This effect
runs after the first render
...
If you've read my article about React hooks' flow, you probably know that statement is inaccurate: useEffect
s run after the browser is painted, which happens not only after the render
but also:
- after React updates the
DOM
, and - after running the
layout effect
s.
Note we don't mention effect
clean-ups since we're speaking about the first render
here. And, besides, the render
itself happens after React runs the lazy state initializers
.
So the first half of the statement is nuanced, but nuances matter. If you're memorizing "useEffect
runs after render
s" you won't be lying to yourself, but you'll only be telling part of the story. "useEffect
runs after the browser has painted the component" tells much more. If you were to memorize one of the two, go for the second one.
But what if I told you the second bit is also wrong?
...and whenever value
changes.
Code speaks for us:
const Component = () => {
let value = 0
useEffect(() => {
console.log('useEffect', value)
}, [value])
return (
<button
onClick={() => {
value++
console.log('onClick', value)
}}
>
Update x
</button>
);
}
Step by step:
Render
Component
, thus"useEffect", 0
will be logged when theeffect
runs after the browser is painted.- Click on the
Update x
button. "onClick", 1
will be logged.- And, according to the definition, since
value
has changed from0
to1
:"useEffect", 1
will be logged.
But it won't. Instead, nothing else will happen.
An effect
doesn't run after one of its dependencies change. An effect
runs once your component rerenders and one of its dependencies have changed.
The first affirmation is only true if its dependencies are state
(or derived from state
) and updated according to React's paradigm.
Don't trust me, trust the code and play with it!
What are effect
s, anyway?
- React components are pure.
effect
s are the place whereside-effect
s are meant to happen. Whatever that means. It's fine if that sounds weird. We won't dive into it today. effect
s synchronize thestate
of your app with the outside world (quotingKent C. Dodds
here).
Here, state
is the important nuance. state
updates (if performed according to React's model) produce new render
cycles. New render
cycles give a chance to effect
s to run if any of their dependencies are different.
useEffect
is your little state synchronization machine.
Rephrasing it
useEffect(() => {
console.log(value)
}, [value])
This
effect
runs:
- after this component is painted in the browser for the first time*, and
- on subsequent component
rerender
s, ifvalue
has changed.
- Actually, whenever React acknowledges a particular instance of a component for the first time, after running its
lazy state initializers
,render
ing it, updating the DOM, running itslayout effect
s and painting those DOM updates.
Or, as Ryan Florence once put it:
The question is not "when does this effect run" the question is "with which state does this effect synchronize with"
useEffect(fn) // all state
useEffect(fn, []) // no state
useEffect(fn, [these, states])
Because, at the end of the day, state
changes are what produce rerender
s, and a rerender
is the only way we can give our effect
s a chance to run.