I have a React application that relies on the value produced by a useSelector
listener within a function that I pass as a property to a child component:
import React from "react";
import { Button } from "./Button"
import {useSelector} from "react-redux";
import {getButtonValue} from "../redux/store"
export const Parent = () => {
const buttonValue = useSelector(getButtonValue);
const onSubmit = () => console.log(`here with value '${buttonValue}'`);
return <Button onSubmit={onSubmit} />
}
and the child button:
import React from "react";
import { useDispatch } from 'react-redux'
import {setButtonValue} from "../redux/store";
type Props = {
onSubmit: () => void;
};
export const Button = ({onSubmit}: Props) => {
const dispatch = useDispatch();
const onButtonClick = () => {
dispatch(setButtonValue("inside button"));
onSubmit();
}
return <div onClick={onButtonClick}>Click me!</div>
}
When a user clicks this button, the console that outputs reads here with value 'initial value'
Whereas I'd expect the selector to have value inside button
, as it was just set to that on the button click.
I'm learning that the reason this value does not seem to be updated is because dispatch()
is an async operation and therefore the console is likely happening before the selector is updated and the onSubmit
definition is re-rendered. Suggestions have been to pass the buttonValue
directly to the function prop on button click. That works in this specific example, but it is much more complex when the Parent
and Button
components are separated by 8-10 other components in our real application (leads to a lot of prop drilling). Is that still the suggested solution, or is there a better way of handling this?
Looking for suggestions on best practices here.
The issue here is that onSubmit
has a stale closure over the selected buttonValue
from before the button is clicked and dispatches the setButtonValue
action to update that state.
Since there's no asynchronous logic involved here I wouldn't recommend updating the redux code to make setButtonValue
an asynchronous action. Props drilling is obviously an issue on its own as well.
My suggestion here would be to simply import the store
and access the current state in the onSubmit
callback.
Example:
import React from "react";
import { Button } from "./Button"
import { useSelector} from "react-redux";
import store from "../redux";
import { getButtonValue } from "../redux/store";
export const Parent = () => {
const buttonValue = useSelector(getButtonValue);
const onSubmit = () => {
const insideButtonValue = getButtonValue(store.getState());
console.log(`Closed over outside button value '${buttonValue}'`);
console.log(`Inside button value '${insideButtonValue}'`);
};
return (
<div>
<span>Hello world!</span>
<Button onSubmit={onSubmit} />
</div>
);
};