Suppose the following:
import * as React from "react";
const useMyHook = ({ element }: { element: HTMLElement | null }) => {
React.useEffect(() => {
if (element) {
console.log("element in hook", element);
}
}, [element]);
};
const MyComponent = React.memo(() => {
const ref = React.useRef<HTMLDivElement | null>(null);
React.useEffect(() => {
if (ref.current) {
console.log("element in parent", ref.current);
}
}, [ref.current]);
useMyHook({ element: ref.current });
return <div ref={ref}>my content</div>;
});
export default function App() {
return <MyComponent />;
}
My issue here is that element in hook
is never printed to the console. I managed to get it to work by removing React.memo
, but I’m wondering why this is happening and how, if possible, I can get this work while still using React.memo
?
I know that refs don’t rerender the component, however my hook checks for changes in a useEffect
, so I’m a little confused as to why it behaves like that.
const MyComponent = () => {
// works fine like this
};
3
Answers
The reason why "element in hook" is not printed to the console when using React.memo is because React.memo is a higher-order component that only re-renders the component if its props have changed.
so you have to use
useCallback
to memorize theuseMyhook
the code would be:
In your example the
ref
gets its value when the div gets mounted.This will assign the value to the
ref
, but youruseMyHook
runs before that happens.{ element: ref.current }
element becomes null. It is true thatuseEffect
will call after the render, but the value that is passed toelement
is passed by value, not by reference. This is the main reason your useEffect inside the component runs as expected, but the useEffect in your hook does not.If you do:
This wont work either.
The way you can fix this is by using the
ref prop
as callback, instead of the result ofuseRef()
.This callback will be called when the div mounts. The node will be the div itself as
HTMLDivElement
About the
React.memo
thing, probably something else causes an extra render and that makes{ element: ref.current }
to give the current value of thediv
that it had from the previous render.As Oktay Yuzcan said, the
element
is passed by value to your hookuseMyHook
. And because ofuseMemo
your hook is not rerender so it is executed with the value ofelement
before it is actually set.An other way to fix your issue is to provide the reference directly to your hook
useMyHook
instead of the value.With this way, your hook will receive always the up to date version of
element.current
.