I have problems with understanding how to properly handle a case. Let's say we have a simplified component:
const XComponent = ({ aCallback }) => {
const [mutation] = useRtkQueryMutationHook()
const doSomeAction = useCallback(() => {
mutation().unwrap().then(() => {
aCallback()
})
}, [mutation, aCallback])
return (
<Button onClick={() => doSomeAction()} />
)
}
this component receives a callback, when a button is clicked, executes a POST, and after it finishes successfully it calls this callback.
As I understand if the aCallback
changes after calling the mutation but before receiving POST response it will not be seen by .then()
call, as it still sees the old, stale, aCallback
.
How can I make it that the proper callback is called when the mutation finishes with success?
For example using useEffect
would not achieve this effect, as useEffect
would be called on each aCallback
change, and not only on successful execution.
I could use useRef
for holding the callback, but then if I have a lot of state that I want to pass to the callback, all it would need to be available in useRef
too. Which feels werid to keep properties and local useState
values in refs.
Of course sometimes I can refactor the code to not have such a requirement, but it is not always feasible.
I could use useRef for holding the callback, but then if I have a lot of state that I want to pass to the callback, all it would need to be available in useRef too. Which feels weird to keep properties and local useState values in refs.
Your hunch is correct to use a React ref to cache a callback value that can be updated at any time. It's not atypical to do this, even for local component state values that need to be accessed in a callback closure.
aCallback
function.useEffect
hook to synchronize the current ref value when the aCallback
prop value updates.const XComponent = ({ aCallback }) => {
const [mutation] = useRtkQueryMutationHook();
const callbackRef = useRef();
// Update callback ref value each time `aCallback` changes.
useEffect(() => {
callbackRef.current = aCallback;
}, [aCallback]);
const doSomeAction = useCallback(() => {
mutation().unwrap()
.then(() => {
callbackRef.current?.();
});
}, [mutation, aCallback]);
return <Button onClick={doSomeAction} />;
}
If you've additional arguments that need to be passed to the callback, use the useEffect
hook to capture these arguments in the callback scope as well.
const XComponent = ({ aCallback }) => {
const [mutation] = useRtkQueryMutationHook();
const callbackRef = useRef();
// Update callback ref value each time `aCallback` and any arguments change.
useEffect(() => {
callbackRef.current = () => aCallback(/* arguments */);
}, [aCallback, /* arguments */]);
const doSomeAction = useCallback(() => {
mutation().unwrap()
.then(() => {
callbackRef.current?.();
});
}, [mutation, aCallback]);
return <Button onClick={doSomeAction} />;
}