1

As explained in this question, React batches state update calls within React event handlers.

React currently will batch state updates if they're triggered from within a React-based event, like a button click or input change. It will not batch updates if they're triggered outside of a React event handler, like an async call.

I.e., a setFoo and setBar would be fused together when called from a React event handler callback.

I'm dealing with various third party libraries, some of them wrap non-React JS libraries. I'm passing them callbacks, and I'm wondering whether these callbacks fall under the "React event handler" category, i.e., whether state update batching applies for them.

Is there a way to manually check if state update are batched, or equivalently, whether some code is executed within a React event handler? I'm thinking about a temporary check like:

let callback = () => {
  // fictitious checks
  console.log(ReactDOM.checkIfUpdatesAreBatched());
  console.log(ReactDOM.checkIfRunningInReactEventHandler());
  setFoo("foo");
  setBar("bar");
}

Edit: The reason I don't want to know this is to make sure that Foo and Bar updates are atomic for consistency reasons. Of course I can fuse the state together, but this has performance issues because a setFooAndBar triggers too many components to re-render, which of course could be mitigated by further memoizing... If I could prove that setFoo and setBar always run with update batching the state separation would be sound.

bluenote10
  • 23,414
  • 14
  • 122
  • 178
  • Slightly related to: [Disable unstable_batchedUpdates() on event handlers in React](https://stackoverflow.com/q/60852219/1804173) – bluenote10 Dec 19 '20 at 13:21

2 Answers2

2

Is there a way to manually check if state update is batched, or equivalently, whether some code is executed within a React event handler?

Just log inside the render function (function component body), when batch occurs, you will get a single log instead of two.

Make sure that only this event causes a re-render.


As a note, a common workaround to batch even in a non-React events is by moving the state into a single object. You can also use another useEffect and derive the other states from it.

Full example:

function Component() {
  const [a, setA] = useState("a");
  const [b, setB] = useState("b");

  console.log({ a });
  console.log({ b });

  const onClick = () => {
    setA("aa");
    setB("bb");
  };

  const onClickPromise = () => {
    Promise.resolve().then(() => {
      setA("aa");
      setB("bb");
    });
  };

  const [batch, setBatch] = useState({ a, b });

  const onClickPromiseBatched = () => {
    Promise.resolve().then(() => {
      setBatch({ a: "aa", b: "bb" });
    });
  };

  useEffect(() => {
    // Will get another render for deriving the state
    // Or just use only the batched state
    setA(batch.a);
    setB(batch.b);
  }, [batch]);

  return (
    <>
      <button onClick={onClick}>Render</button>
      <button onClick={onClickPromise}>Render with promise</button>
      <button onClick={onClickPromiseBatched}>
        Render with promise batched
      </button>
    </>
  );
}

Edit Batch Example

Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
  • You're right, render counting is probably the only option. Perhaps I could even write regression tests to guarantee single re-renderings etc. Note that you probably meant the opposite: _when batch occurs, you will get one log instead of two._ – bluenote10 Dec 19 '20 at 14:01
1

There is no way to do this. Nor would you ever actually need this. If you have functionality that depends on pieces of state having specific values, use a useEffect with the appropriate pieces of state in the dependency array and in the callback you give useEffect conditionally check the values.

If you have a very specific use case which actually requires no batching (and you are sure you need to opt out, 99.999999% of people don’t) there exists

ReactDOM.flushSync(() => {
  // this setState won't be batched
  setState(something)
})

This isn’t strictly related, but Dan Abermov of the React team discusses batch updating with a user in regards to input focusing: https://github.com/facebook/react/issues/18402

callmetwan
  • 1,205
  • 2
  • 14
  • 31
  • _Nor would you ever actually need this._ I need to make sure that `Foo` and `Bar` updates are atomic for consistency reasons. Of course I can fuse the state together, but this has performance issues. If I could prove that `setFoo` and `setBar` always run with update batching the state separation would be sound. I'm just looking for an alternative to read the entire code of the involved third party libraries to decide if they really always call me from within React event handlers. – bluenote10 Dec 19 '20 at 13:28
  • Take a look at that GitHub issue I posted. There is quite a bit of back and forth about being able to have these atomic calls. There is no way to know if state calls will be batched or not. Based on the information Dan provided in that discussion you may be able to prevent batching from happening for your call or isolate your calls so that batching doesn’t matter. – callmetwan Dec 19 '20 at 13:43