22
loading...
This website collects cookies to deliver better user experience
flushSync
utility provided by react-dom
.flushSync
is and how it can useful through an example.App
component that has a todos
state and returns a list of todos along with a form.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>
);
}
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.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>
);
};
todos
change just scroll the container to the bottom.useEffect(() => {
listRef.current.scrollTop = listRef.current.scrollHeight;
// listRef is simply a ref attached to the ul
}, [todos]);
useEffect(() => {
const lastTodo = listRef.current.lastElementChild;
lastTodo.scrollIntoView();
}, [todos]);
useLayoutEffect
hook in this situation in case you observe any jitters in scrolling).onAdd
handler.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
const onAdd = (newTask) => {
setTodos([...todos, { id: uuid(), task: newTask }]);
listRef.current.scrollTop = listRef.current.scrollHeight;
};
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.todos
state has been updated. And that's where flushSync
comes handy.flushSync
, we need to import it from react-dom
: import { flushSync } from "react-dom";
setTodos
call inside flushSync
handler (as shown below).const onAdd = (newTask) => {
flushSync(() => {
setTodos([...todos, { id: uuid(), task: newTask }]);
});
listRef.current.scrollTop = listRef.current.scrollHeight;
};
flushSync
.