Home > Software design >  Arrow function does not notify redux state changes
Arrow function does not notify redux state changes

Time:12-19

I have a react component in react native that I want to handle hardwareBackButton manually. I have different behavior when a redux state is true or false in backHandler function that I pass to hardwareBackPressListener.

const brandSelected = useSelector(state => state.map.brandSelected);

I have this useSelector in my component to access the state. and I have useEffect function that I monitor the changes of this state: (that has correctly work and log the state when it changes to true or false.

React.useEffect(() => {
  console.log(brandSelected); // this is false correctly   
}, [brandSelected]);

and finally I have a backHandler function that I pass it to hardwareBackPress Listener.

React.useEffect(() => {
  BackHandler.addEventListener('hardwareBackPress', backHandler);
  return () => {
    BackHandler.removeEventListener('hardwareBackPress', backHandler);
  };
}, []);

and backHandler function:

const backHandler = () => {
  console.log('check, backhandler', brandSelected) // it logs true continuously
  if (brandSelected === true) {
    dispatch(
      dispatchItemToRedux({
        type: CATEGORIES_SELECTION,
        payload: {
          brandSelected: false,
        },
      }),
    );
    return true;
  }
  popScreen(Screens.Map);
  return true;
};

But this function does not notify that the brandSelected state changed. the first time it works correctly and dispatch function and changes the redux state correctly and useEffect function log false correctly. but in other tries it does not work correctly and nothing changed!!

CodePudding user response:

The issue here is a stale enclosure of the brandSelected in the backHandler function you passed to the "hardwareBackPress" event listener on the initial render cycle. backHandler function only ever has the value from the initial render cycle and will never update/re-enclose an updated value.

To resolve you should cache the backHandler state value in a React ref that you can reference in the callback handler.

const brandSelected = useSelector(state => state.map.brandSelected);
const brandSelectedRef = React.useRef(brandSelected);

useEffect(() => {
  brandSelectedRef.current = brandSelected;
}, [brandSelected]);

...

const backHandler = () => {
  console.log('check, backhandler', brandSelectedRef.current)
  if (brandSelectedRef.current) {
    dispatch(
      dispatchItemToRedux({
        type: CATEGORIES_SELECTION,
        payload: {
          brandSelected: false,
        },
      }),
    );
    return true;
  }
  popScreen(Screens.Map);
  return true;
};

An alternative would be to move backHandler into the useEffect hook setting the event listeners and use the brandSelected state as a dependency so the updated state value is re-enclosed in the callback.

React.useEffect(() => {
  const backHandler = () => {
    console.log('check, backhandler', brandSelected)
    if (brandSelected) {
      dispatch(
        dispatchItemToRedux({
          type: CATEGORIES_SELECTION,
          payload: {
            brandSelected: false,
          },
        }),
      );
      return true;
    }
    popScreen(Screens.Map);
    return true;
  };

  BackHandler.addEventListener('hardwareBackPress', backHandler);
  return () => {
    BackHandler.removeEventListener('hardwareBackPress', backHandler);
  };
}, [brandSelected]);
  • Related