What does flushSync() do in React?
Asked Answered
P

3

25

I saw Dan Abramov's demo project at JSConf Iceland, but I didn't understand why he used flushSync in this code:

  import { flushSync } from 'react-dom';

  debouncedHandleChange = _.debounce(value => {
    if (this.state.strategy === 'debounced') {
      flushSync(() => {
        this.setState({value: value});
      });
    }
  }, 1000);

What does flushSync do in react?

Pl answered 4/7, 2020 at 6:10 Comment(0)
P
22

flushSync flushes the entire tree and actually forces complete re-rendering for updates that happen inside of a call, so you should use it very sparingly. This way it doesn’t break the guarantee of internal consistency between props, state, and refs.

It is not documented properly yet. Read more here https://github.com/facebook/react/issues/11527

Poet answered 4/7, 2020 at 6:11 Comment(1)
Thanks for the wonderful link. As an addition also take a look at github.com/roginfarrer/react-collapsed/commit/… where Dan Abramov describes that you can use flushSync to flush data synchronously.Hereat
G
14

Chilarai has a great answer! (Maybe due to time of answer and now having more documentation) I do not agree with the idea of using it very sparingly. As a lot of the time it will stop the need for an extra unneeded useEffect (It is also against how Dan suggests to use useEffect https://twitter.com/dan_abramov/status/1501737272999301121).

Here is a great example of what it achieves based on Dan Abramov's code:

The requirement for this code is an Auto-scroll on new message (I use this code currently and it is very effective).

Before, in this example we are unable to scroll after setMessages as the DOM will not be rendered yet and a hook is needed.

export default function App() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    let socket = connect();

    socket.onMessage((message) => {
      setMessages((m) => [
        ...m,
        message
      ])
    });
    
    return () => {
      socket.disconnect();
    };
  }, [])

  function scrollToLastMessage() {
    // ...
  }

  // NOTE: In order to scroll without using flushSync a unneeded hook
  useEffect(() => {
    scrollToLastMessage();
  }, [messages])
}

After, in this example the DOM is forced to be flush synced and then we can call in the order we wanted.

export default function App() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    let socket = connect();

    function scrollToLastMessage() {
      // ...
    }

    socket.onMessage((message) => {
      flushSync(() => {
        setMessages((m) => [
          ...m,
          message
        ])
      })
      scrollToLastMessage();
    });
    
    return () => {
      socket.disconnect();
    };
  }, [])
}

This code comes from Dan in this video https://www.youtube.com/watch?v=uqII0AOW1NM at 35:25 I would recommend watching that!

I would also highly recommend adding flushSync to your toolbelt!

Giggle answered 21/6, 2022 at 0:34 Comment(0)
D
4

After days about thinking of it, I think I finally understand it. Thanks to @Alex Dunlop's answer.

For anyone wanna see a simple handson example usage of flushSync api, plesae see my codesandbox example here.

import { useEffect, useState } from "react";
import { flushSync } from "react-dom";

export default function App() {
  const [c, setC] = useState(0);

  const inc1 = () => {
    setC((c) => c + 1);
    console.log(document.getElementById("myId").innerText); // old value
  };

  const inc2 = () => {
    flushSync(() => {
      setC((c) => c + 1);
    });
    console.log(document.getElementById("myId").innerText); // new value
    // However below log will still point to old value
    // console.log(c); // old value
  };

  return (
    <div className="App">
      Count: <div id="myId">{c}</div>
      <button onClick={inc1}>without flushSync</button>
      <button onClick={inc2}>with flushSync</button>
    </div>
  );

Also, another aspect of flushSync is that one must *NOT* use this in useEffect hooks as react throws warning about it i.e., Warning: flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task.:

const BadPattern = () => {
  const [c, setC] = useState(0);

  // YOU CAN NOT USE `flushSync` in useEffect hook
  // FROM REACT: Warning: flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task.
  useEffect(() => {
    flushSync(() => {
      setC((c) => c + 1);
    });
    console.log(c);
  }, []);

  return (
    <div className="App">
      <h2>BAD PATTERN</h2>
      Count: <div>{c}</div>
    </div>
  );
};
Delores answered 28/12, 2022 at 22:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.