Have you used `flushSync` in React?
Tags: react, javascript, webdev, programming
Published: 2021-12-23 08:21:28

In this post we'll discuss about the `flushSync` utility provided by `react-dom`.

Let's try and understand what `flushSync` is and how it can useful through an example.

As always, it's a simple todo example but the point to note here is that the todo container has fixed height and is scrollable.

So, there's our `App` component that has a `todos` state and returns a list of todos along with a form.
```jsx
export default function App() {
  const [todos, setTodos] = useState(mockTodos);

  const onAdd = (newTask) => {
    setTodos([...todos, { id: uuid(), task: newTask }]);
  };

  return (
    <section className="app">
      <h1>Todos</h1>
      <ul style={{ height: 200, overflowY: "auto" }}>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.task}</li>
        ))}
      </ul>
      <AddTodo onAdd={onAdd} />
    </section>
  );
}
```

The `AddTodo` component is also fairly simple, it just manages the input state and once the form is submitted it calls the `onAdd` prop with the new todo.
```jsx
const AddTodo = ({ onAdd }) => {
  const [taskInput, setTaskInput] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();

    if (!taskInput.trim()) return;
    setTaskInput("");
    onAdd(taskInput.trim());
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Your Task"
        value={taskInput}
        onChange={(e) => setTaskInput(e.target.value)}
      />
      <button>Add Task</button>
    </form>
  );
};
```

Now that we've an understanding of how our code works, suppose we want to add a functionality where every time a new todo is added, the container is scrolled to its bottom so that the newly added todo is visible to the user.

Think for while and figure out how you would go about implementing this functionality.

## Using `useEffect` hook
You might be thinking of using the effect hook. So, every time the `todos` change just scroll the container to the bottom. 
```jsx
useEffect(() => {
  listRef.current.scrollTop = listRef.current.scrollHeight;
  // listRef is simply a ref attached to the ul
}, [todos]);
```

_OR_

```jsx
useEffect(() => {
  const lastTodo = listRef.current.lastElementChild;
  lastTodo.scrollIntoView();
}, [todos]);
```

Both of the above scrolling logics work fine (_you might even want to use the `useLayoutEffect` hook in this situation in case you observe any jitters in scrolling_).

But, I would not want to put this in either of these hooks, let me explain why.

The DOM manipulation (_scrolling in this case_) that we're trying to do here is a **side effect** (_something that doesn't happen during rendering_) and in React side effects usually happen inside event handlers, so in my opinion the best place to put this would be inside the `onAdd` handler.

Also, if you go by the docs `useEffect` should be your last resort, when you've exhausted all other options but haven't found the right event handler.

> If you’ve exhausted all other options and can’t find the right event handler for your side effect, you can still attach it to your returned JSX with a `useEffect` call in your component. This tells React to execute it later, after rendering, when side effects are allowed. **However, this approach should be your last resort**. [DOCS](https://beta.reactjs.org/learn/keeping-components-pure#where-you-can-cause-side-effects)

## Scrolling logic inside the handler
If you simply put the scrolling logic inside the handler (as shown below), you would notice that you're not exactly getting the desired results.

```jsx
const onAdd = (newTask) => {
  setTodos([...todos, { id: uuid(), task: newTask }]);

  listRef.current.scrollTop = listRef.current.scrollHeight;
};
```

Because `setTodos` is not synchronous, what happens is you scroll first and then the `todos` actually get updated. So, what's in view is not the last todo but second to last.

So, to get it working as expected we would have to make sure that the logic for scrolling runs only after the `todos` state has been updated. And that's where `flushSync` comes handy.

## Using `flushSync`

To use `flushSync`, we need to import it from `react-dom`: `import { flushSync } from "react-dom";`

And now we can wrap the `setTodos` call inside `flushSync` handler (as shown below).
```jsx
const onAdd = (newTask) => {
  flushSync(() => {
    setTodos([...todos, { id: uuid(), task: newTask }]);
  });

  listRef.current.scrollTop = listRef.current.scrollHeight;
};
```

Now we have made sure that the state update happens synchronously and the logic for scrolling is executed only after the state has been updated.

{% codesandbox react-flush-sync-bo1in %}

That's it for this post, let me know situations where you would want to use `flushSync`. 

Peace ✌