This is the 16th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021
Translate: beta.reactjs.org/learn/manip…
Because React already processes the DOM structure based on render’s output, your components won’t need to manipulate the DOM very often. However, there may be times when you need to manipulate DOM elements managed by React, for example, to focus on a node, scroll to the node, or calculate its width and height. React has no built-in method to do this, so you’ll need ref to point to the DOM node.
In this series of articles you will learn:
- How do I access DOM nodes managed by React using the REF attribute
- How to integrate JSX
ref
Attribute associated withuseRef
hook - How do I access the DOM nodes of other components
- In which case it is safe to modify the DOM managed by React
For an introduction to and examples of refs, see my previous series of articles, useRef, which is easy to parse
series
- How do I manipulate the DOM using ref? (1) Use ref to access DOM node
- How do I manipulate the DOM using ref? (2) Use examples
- How do I manipulate the DOM using ref? (c) How to use the ref callback to manage the list of ref
- How do I manipulate the DOM using ref? The forwardRef accesses its component’s DOM node
How does React assign refs
In React, each update is divided into two phases:
- During Render, React calls your component to determine what should be displayed on the screen.
- During commit, React applies the changes to the DOM.
Normally, you don’t want to visit Refs during Render. This also applies to refs that hold DOM nodes, which is the case in this series. During the first render process, the DOM node has not yet been created, so the ref. Current value is null. Also during the render update, the DOM node has not been updated. So it’s too early to read their values.
React sets the ref. Current value to null during commit and before DOM updates, React sets the affected ref. Current value to null. After DOM updates, React immediately sets them to the appropriate DOM node.
Typically, you will access refs from an event handler. If you want to do something with ref but don’t have a specific event to do it, you might need an effect, which we’ll discuss later.
Synchronous refresh status
Consider code like this and expect it to add a new to-do item and scroll down to the last child of the list. But the reality, for some reason, is that it always scrolls to the to-do list before the last added to-do list:
import { useState, useRef } from 'react';
export default function TodoList() {
const listRef = useRef(null);
const [text, setText] = useState(' ');
const [todos, setTodos] = useState(
initialTodos
);
function handleAdd() {
const newTodo = { id: nextId++, text: text };
setText(' ');
setTodos([ ...todos, newTodo]);
listRef.current.lastChild.scrollIntoView({
behavior: 'smooth'.block: 'nearest'
});
}
return (
<>
<button onClick={handleAdd}>
Add
</button>
<input
value={text}
onChange={e= > setText(e.target.value)}
/>
<ul ref={listRef}>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</>
);
}
let nextId = 0;
let initialTodos = [];
for (let i = 0; i < 20; i++) {
initialTodos.push({
id: nextId++,
text: 'Todo #' + (i + 1)}); }Copy the code
The problem is these two lines of code:
setTodos([ ...todos, newTodo]);
listRef.current.lastChild.scrollIntoView();
Copy the code
In React, status updates are queued. Often, that’s what you want. However, this can cause a problem because setTodos does not update the DOM immediately. Therefore, when you scroll down to the last element of the list, the new backlog has not been updated to the DOM. This is why scrolling is always “behind”.
To solve this problem, you can force React to synchronously update (” refresh “) the DOM. To do this, import flushSync from react-dom and wrap the status update in a flushSync call:
flushSync(() = > {
setTodos([ ...todos, newTodo]);
});
listRef.current.lastChild.scrollIntoView();
Copy the code
This instructs React to synchronize updates to the DOM immediately after the code wrapped in flushSync is executed. So when you try to scroll to the last todo, it’s already in the DOM:
import { useState, useRef } from 'react';
import { flushSync } from 'react-dom';
export default function TodoList() {
const listRef = useRef(null);
const [text, setText] = useState(' ');
const [todos, setTodos] = useState(
initialTodos
);
function handleAdd() {
const newTodo = { id: nextId++, text: text };
flushSync(() = > {
setText(' ');
setTodos([ ...todos, newTodo]);
});
listRef.current.lastChild.scrollIntoView({
behavior: 'smooth'.block: 'nearest'
});
}
return (
<>
<button onClick={handleAdd}>
Add
</button>
<input
value={text}
onChange={e= > setText(e.target.value)}
/>
<ul ref={listRef}>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</>
);
}
let nextId = 0;
let initialTodos = [];
for (let i = 0; i < 20; i++) {
initialTodos.push({
id: nextId++,
text: 'Todo #' + (i + 1)}); }Copy the code