0

Question

Is the normal and expected behavior of a react reducer to ignore state updates in a some kind of "debounced" way?

I have implemented a snack where you can see that with 2 instant state updates via reducer, the screen is only rendered one time.

https://snack.expo.dev/oXd96Nbq8

Check the console logs. Why is this happening?

This is really annoying, since I can't run side-effects since the first state update is not detected? Any ideas?

Code

import React, { useReducer } from 'react';
import Constants from 'expo-constants';

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'toggle':
      const newState = { count: state.count === 1 ? 0 : 1 };
      console.log("Expected state", newState);
      return newState;
  }
}


export default function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  console.log("Rendered state", state);

  const handleOnPress = () => {
    dispatch({type: 'toggle'}); // 1 expected to be rendered... but not rendered because of the rollback?

    try {
      throw new Error("Forced error...");
    } catch(err) {
      console.log(err);
      // Rollback
      dispatch({type: 'toggle'});  // 0 expected to be rendered...
    }
  }

  return (
    <>
      Count: {state.count}
      <button onClick={handleOnPress}>toggle</button>
    </>
  );
}
Victor Molina
  • 2,353
  • 2
  • 19
  • 49
  • You dispatched two actions in the same callback scope/render cycle, the second reverting the first. Your logs clearly show both actions were dispatched and handled by the reducer function. Nothing was debounced. What are you expecting to happen? – Drew Reese Jan 25 '22 at 22:18
  • @DrewReese in my original code, `throw new Error...` is just an asynchronous function call. If the promise is rejected, the rollback is executed, updating the UI to the previous state. What I don't understand is why if `throw new Error...` is replaced with a timeout or an asynchronous call, both states are **rendered**. Whereas, if the error is thrown directly, without delay, only the latest update is detected (by the screen, not the reducer). – Victor Molina Jan 25 '22 at 22:22
  • 1
    One is synchronous while the other is not. It's all React state under the hood, so any enqueued state updates in a synchronous function are processed before rerendering the component. When you create an asynchronous callback the updates are not bulk processed and you are allowing React to possibly rerender before the asynchronous calls resolve. – Drew Reese Jan 25 '22 at 22:25
  • 2
    related to the batching sync vs async thing: https://stackoverflow.com/questions/53048495/does-react-batch-state-update-functions-when-using-hooks – Nikki9696 Jan 25 '22 at 22:26
  • thx very much! didn't know about the batched behavior! – Victor Molina Jan 25 '22 at 22:31

0 Answers0