bren
bren

Reputation: 123

React state not updating in click event with two functions, but updating with one function

This one has turned out to be a head scratcher for a while now...

I have a react component that updates state on a click event. The state is a simple boolean so I'm using a ternary operator to toggle state.

This works however as soon as I add a second function to the click event state no longer updates. Any ideas why this is happening and what I'm doing wrong?

Working code...

  export default function Activity(props) {
   const [selected, setSelected] = useState(false);

   const selectActivity = () => {
     selected ? setSelected(false) : setSelected(true);
     return null;
   };

   const clickHandler = (e) => {
       e.preventDefault();
       selectActivity();
     };

      return (
        <div
          onClick={(e) => clickHandler(e)}
          className={`visit card unassigned ${selected ? 'selected' : null}`}
        >
          //... some content here
        </div>
      );
     }

State not updating...

  export default function Activity(props) {
   const [selected, setSelected] = useState(false);

   const selectActivity = () => {
     selected ? setSelected(false) : setSelected(true);
     return null;
   };

   const clickHandler = (e) => {
       e.preventDefault();
       selectActivity();
       props.collectVisitsForShift(
          props.day,
          props.startTime,
          props.endTime,
          props.customer
       );
     };

      return (
        <div
          onClick={(e) => clickHandler(e)}
          className={`visit card unassigned ${selected ? 'selected' : null}`}
        >
          //... some content here
        </div>
      );
     }

Upvotes: 0

Views: 3427

Answers (2)

bren
bren

Reputation: 123

I went for a walk and figured this one out. I'm changing state in the parent component from the same onClick event, which means the child component re-renders and gets its default state of 'false'.

I removed the state change from the parent and it works.

Thanks to Andrei for pointing me towards useCallback!

Upvotes: 1

Andrei
Andrei

Reputation: 1873

I loaded your code in a CodeSandbox environment and experienced no problems with the state getting updated. But I don't have access to your collectVisitsForShift function, so I couldn't fully reproduce your code.

However, the way you're toggling the state variable doesn't respect the official guidelines, specifically:

If the next state depends on the current state, we recommend using the updater function form

Here's what I ended up with in the function body (before returning JSX):

const [selected, setSelected] = useState(false);

// - we make use of useCallback so toggleSelected
//   doesn't get re-defined on every re-render.
// - setSelected receives a function that negates the previous value

const toggleSelected = useCallback(() => setSelected(prev => !prev), []);

const clickHandler = (e) => {
    e.preventDefault();
    toggleSelected();
    props.collectVisitsForShift(
        props.day,
        props.startTime,
        props.endTime,
        props.customer
    );
};

The documentation for useCallback.

Upvotes: 0

Related Questions