In React, both useEffect
and useMemo
have a dependencies argument.
I naively thought they worked identically: whenever the values in that dependencies argument changed, I thought the useEffect
or useMemo
callback would be run, and that the only difference was just timing (useMemo
runs before the render, while useEffect
runs after).
In general, they are identical. But when I have a ref as a dependency, they aren't:
const Foo = ()=> {
const ref = useRef();
useEffect(() => console.log('effect ref', ref), [ref]);
useMemo(() => console.log('memo ref', ref), [ref]);
return <div ref={ref}>Foo</div>
}
Sandbox Link (Click the console icon in the upper-right to see the logs)
As I understand it Foo
should render twice. The first time through, ref.current
will be undefined
, and the second time it will be set to the <div>
. Thus, I'd expect that to see two useEffect
logs of effect ref div
, because the ref
will be set both after render #1 and render #2. And I do ...
effect ref {current: div}
effect ref {current: div}
However, with the memo logs I never see the div
logged. Instead, I just see the same thing repeated:
memo ref undefined
memo ref undefined
That's expected for the first call, since the component hasn't rendered yet ... but before the second render, after the first, shouldn't the ref
be set to the <div>
, and so shouldn't useMemo
log it? Instead, it seems useMemo
is never run.
In short, while I mostly understand how useMemo
works, it seems I'm missing some key detail that explains why it will never log a ref
's value, and I'd appreciate any help explaining why.
P.S. I realize I could probably solve all this by making useRef
depend on a state variable, and then making a useEffect
which updates that state variable when the ref
changes ... but I'm more focused on trying to understand the problem first before I solve it.
This behavior is due to React.StrictMode
, not from using a ref as a dependency.
A ref is a stable object reference across renders, and useEffect
and useMemo
use a referential equality check to determine if their dependencies have changed. Therefore, under normal circumstances (a prod build for example) these hooks would only run one time.
Since you are seeing evidence that they are running more than once, the answer is Strict Mode.
The explanation for this comes from a few different sections of the documentation.
https://react.dev/reference/react/StrictMode
Your components will re-run Effects an extra time to find bugs caused by missing Effect cleanup
https://react.dev/reference/react/useMemo#caveats
In Strict Mode, React will call your calculation function twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. If your calculation function is pure (as it should be), this should not affect your logic. The result from one of the calls will be ignored.
https://react.dev/blog/2022/03/29/react-v18#new-strict-mode-behaviors
It's also possible (but impossible to tell) that it could be caused by React 18 StrictMode re-mounting every component.