I'm trying to understand the practical difference between having a side effect in a component function, vs having it within an effect which has no dependency array passed in (and as such should fire on every render). From what I can observe, they both run at the same frequency. I realize that effects allow for cleanup at the proper time, but I'm simply curious about the scenario where cleanup is not a factor.
The following CodePen shows what I'm talking about.
https://codepen.io/benrhere/pen/GRyvXZZ
The important piece is:
function EffectVsFunctionQuestion() {
const [count, setCount] = React.useState(0);
React.useEffect(()=>{
console.log("incremented within hook")
});
console.log("incremented within component function")
...
}
Side-effects issued from the useEffect
hook have the benefit of being triggered at most once per render cycle. The render cycle here means the "commit phase" when React has computed the next view and commits it to the DOM.
This should not be confused with what React calls the "render phase" when it renders out the component (and children and entire ReactTree) to compute what changed and needs to be committed to the DOM.
The entire function body of a React function component is the "render" function. As you can see in the diagram, any unintentional side-effects in the render
or body will occur during the "render phase" which can can be paused, aborted, or restarted (i.e. run again) by React. Note that the "render phase" is to be pure and free from side-effects.
The "commit phase" the component can work with the DOM and run side-effects.
Why does this matter? How to tell the difference.?
React actually ships with a StrictMode
component that helps you detect unexpected side-effects.
Detecting unexpected side effects
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
- Class component
constructor
,render
, andshouldComponentUpdate
methods- Class component static
getDerivedStateFromProps
method- Function component bodies
- State updater functions (the first argument to
setState
)- Functions passed to
useState
,useMemo
, oruseReducer
Here's an example sandbox demo that demonstrates the unexpected side effects.
Note that the unexpected effect is doubled.
Code:
const externalValue1 = { count: 0 };
const externalValue2 = { count: 0 };
function EffectVsFunctionQuestion() {
const [state, setState] = React.useState(0);
React.useEffect(() => {
externalValue1.count++;
console.log("incremented within hook", externalValue1.count);
});
externalValue2.count++;
console.log("incremented within component function", externalValue2.count);
return (
<button type="button" onClick={() => setState((c) => c + 1)}>
Render
</button>
);
}