This is the 19th day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 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 JSXrefAttribute associated withuseRefhook
  • 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 do I manipulate the DOM using ref? React State Updates DOM synchronously
  • How do I manipulate the DOM using ref? (6) Best practices
  • How do I manipulate the DOM using ref? 7. Summary and practice

UseImperativeHandle exposes specific apis to its components

In the previous example, MyInput exposes the raw DOM input element. This allows the parent component to call focus() on it. However, this also allows the parent component to do other things. For example, change its CSS style. In unusual cases, you may want to limit exposed functionality. You can do this using useImperativeHandle:

import {
  forwardRef, 
  useRef, 
  useImperativeHandle
} from 'react';

const MyInput = forwardRef((props, ref) = > {
  const realInputRef = useRef(null);
  useImperativeHandle(ref, () = > ({
    // Focus only, nothing else
    focus(){ realInputRef.current.focus(); }}));return <input {. props} ref={realInputRef} />;
});

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}
Copy the code

Here, realInputRef in MyInput holds the actual input DOM node. However, useImperativeHandle instructs React to provide the value of your own special object as a reference to the parent component. So inputRef. The current Form component only has the Focus method. In this case, the ref “handle” is not a DOM node, but a custom object that you create in the useImperativeHandle call.

A flushSync practice

This image rotation has a “Next” button that toggles active images. Click to scroll the gallery horizontally to the active image. You need to call scrollIntoView() on the DOM node of the active image:

node.scrollIntoView({
  behavior: 'smooth'.block: 'nearest'.inline: 'center'
});
Copy the code
import { useState } from 'react';

export default function CatFriends() {
  const [index, setIndex] = useState(0);
  return (
    <>
      <nav>
        <button onClick={()= > {
          if (index < catList.length - 1) {
            setIndex(index + 1);
          } else {
            setIndex(0);
          }
        }}>
          Next
        </button>
      </nav>
      <div>
        <ul>
          {catList.map((cat, i) => (
            <li key={cat.id}>
              <img
                className={
                  index= = =i ?
                    'active' :"'}src={cat.imageUrl}
                alt={'Cat #' + cat.id} / >
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}

const catList = [];
for (let i = 0; i < 10; i++) {
  catList.push({
    id: i,
    imageUrl: 'https://placekitten.com/250/200?image=' + i
  });
}
Copy the code

The solution

You can declare a selectedRef and then conditionally pass it to the current image:

<li ref={index === i ? selectedRef : null} >Copy the code

When index === I, the image is selected and

  • will be assigned selectedRef. React will ensure that the selected selectedref.current always points to the correct DOM node.
  • Note that the call to flushSync synchronously updates the state before React updates the DOM before scrolling. Otherwise, selectedref.current always points to the previously selected item.

    import { useRef, useState } from 'react';
    import { flushSync } from 'react-dom';
    
    export default function CatFriends() {
      const selectedRef = useRef(null);
      const [index, setIndex] = useState(0);
    
      return (
        <>
          <nav>
            <button onClick={()= >{ flushSync(() => { if (index < catList.length - 1) { setIndex(index + 1); } else { setIndex(0); }}); selectedRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); }}> Next</button>
          </nav>
          <div>
            <ul>
              {catList.map((cat, i) => (
                <li
                  key={cat.id}
                  ref={index= = =i ?
                    selectedRef :
                    null
                  }
                >
                  <img
                    className={
                      index= = =i ?
                        'active'
                        :"'}src={cat.imageUrl}
                    alt={'Cat #' + cat.id} / >
                </li>
              ))}
            </ul>
          </div>
        </>
      );
    }
    
    const catList = [];
    for (let i = 0; i < 10; i++) {
      catList.push({
        id: i,
        imageUrl: 'https://placekitten.com/250/200?image=' + i
      });
    }
    Copy the code

    To summarize, in order to scroll to the correct DOM node, state needs to be updated synchronously, then ref needs to be updated.