Search code examples
javascriptreactjsreduxreact-lifecycle

Why my callback is calling multiple time with redux


I am writing a react application with redux, with avoiding the react-redux, which is technically possible if we manually handle all the dispatched events. Here is the sample code.

The index.html

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>
</head>
<body>
  <div id="app"></div>
  <script>

  const appState= {
    count: 0,
  }

  const reducer = (state, action) => {
    if (typeof state === 'undefined') state = appState;
    switch (action.type) {
      case 'INCREMENT':
        return {count: state.count+1}
      case 'DECREMENT':
        return {count: state.count-1}
      default:
        return state
    }
  }

  var store = Redux.createStore(reducer);

  const App = () => {
    const [val, setVal] = React.useState(0);
  
    handleClick = () => {
      store.dispatch({type: 'INCREMENT'})
    }
  
    const unsubscribe = store.subscribe(() => {
      const state = store.getState();
      console.log("Listener is called")
      setVal(state.count);
    });
  
    /* unsubscribe() */;
  
    return (
      <div>
        <span>{val}</span>
        <button onClick={handleClick}>Click</button>
      </div>
    );
  }
  ReactDOM.render(<App />, document.querySelector("#app"))
  </script>
</body>
</html>

Here if I click the button the first time it prints the log to console once, but when I click the button second time it prints the the statement twice on the log, which indicates that callback from the subscribe is being called twice, why it's happening and how can I prevent that?


Solution

  • Looks like your component is subscribing to the store each render cycle, and because the subscription callback updates component state another render cycle is triggered.

    Chances are you likely only want the component to subscribe once to your store.

    You can use an effect to subscribe once to log the state when it updates. Use the effect cleanup function to unsubscribe.

    const App = () => {
      const [val, setVal] = React.useState(0);
    
      handleClick = () => {
        store.dispatch({type: 'INCREMENT'})
      }
    
      useEffect(() => {
        const unsubscribe = store.subscribe(() => {
          const state = store.getState();
          console.log("Listener is called", state.count);
          setVal(state.count);
        });
    
        /* unsubscribe() */;
        return unsubscribe; // <-- return cleanup function
      }, []); // <-- empty dependency array to run once on mount
    
      return (
        <div>
          <span>{val}</span>
          <button onClick={handleClick}>Click</button>
        </div>
      );
    }