-
-
Notifications
You must be signed in to change notification settings - Fork 5.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Memoize mutation callbacks #8526
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! It seems good to me, but I'd feel safer having @fzaninotto review that one too 😁
This won't work - because passing an empty array as dependency means the callback will never be updated. The import * as React from 'react';
import { useCallback } from 'react';
// allow the hook to work in SSR
const useLayoutEffect =
typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
/**
* Alternative to useCallback that doesn't update the callback when dependencies change
*
* @see https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback
* @see https://github.com/facebook/react/issues/14099#issuecomment-440013892
*/
export const useEvent = <Args extends unknown[], Return>(
fn: (...args: Args) => Return,
): ((...args: Args) => Return) => {
const ref = React.useRef<(...args: Args) => Return>(() => {
throw new Error('Cannot call an event handler while rendering.');
});
useLayoutEffect(() => {
ref.current = fn;
// eslint-disable-next-line react-hooks/exhaustive-deps
});
return useCallback((...args: Args) => ref.current(...args), []);
}; |
I think it should always fire:
The dependency array here includes the function that we pass and it will be different on each render (that's the problem we're solving after all!), plus all the dependencies we pass (but we pass an empty array so it doesn't matter). Am I missing something? |
You're right, it will work, but the code isn't clear enough. I think passing a dependency that is always new to force a re-execution of the effect is a useless indirection. We should pass no dependency at all. |
Okay, I'll add the shim you've proposed and use it instead, makes sense to signal the intent clearer + it might be useful for other things in the future. |
wait, in fact you should replace the Sorry for the back and forth, but this is the part of React I'm the less comfortable with. |
I believe we all are 😅 |
No problem, I'm always happy to learn. I'll see what I can do tomorrow. |
Just to confirm, I'll also remove the dependencies here while replacing the hook:
This lambda is recreated on each render so the dependency array is indeed useless. The only situation it'd be useful is if the function that's passed to useEventCallback is itself memoized (with useCallback for example) but this useStore hook is the only one that uses useEventCallback so I guess it's safe to make the replacement.
|
Seems good to me. I still have to test that it actually reduces re-renders using the React profiler. |
Happy New Year! I finally took some time to test your PR. I didn't find any different while using the simple example with or without your changes. Could you please set up a story that demonstrates the change? Also, this is technically a new feature, so please rebase to PR against |
Happy New Year! I rebased on next. I suppose it'd be hard to provide a working example, in my case I use a complex component that completely replaces the edit view of a resource, it's also put into its own context so re-renders might be triggered by many things including that context value change. The code that caused issues was my custom hook that marks the currently opened item as "seen" after 5 seconds if the user doesn't navigate away. So that hook starts a timer in I'm truly sorry but I can't come up with a natural example of these re-renders to occur during the more or less classic RA usage, the default list/edit/show components don't re-render spontaneously. And my custom component is too big and niche to adapt it for an example. Most of the time the current behavior shouldn't hit users I think but eventually it will. |
Thanks! |
Fixes #8518.