Search code examples
reactjsreduxreact-reduxredux-toolkit

Using useSelector value in function property


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.

An example of this in playground


Solution

  • 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>
      );
    };
    

    Edit using-useselector-value-in-function-property