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.
2
Answers
Use effect would trigger an action based off the changed ref where has memo will be a variable, and you will need to return something.
For example:
I think whats happening in your question in particular is that you are passing the ref when you should be passing
ref.current
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
anduseMemo
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
https://react.dev/reference/react/useMemo#caveats
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.