DEV Community

Ayako yk
Ayako yk

Posted on

useRef 2: DOM Manipulation

In the previous blog post, I introduced the fundamental concept of useRef, discussing topics such as "Referencing a value with a ref" and "Manipulating the DOM with a ref." After publishing the post, I received valuable advice and insights regarding DOM manipulation, encouraging me to delve deeper into this topic. The information and examples presented in this blog post are based on the React Documentation.
Manipulating the DOM with Refs

Recap from the last blog:

Syntax
import { useRef } from 'react';
const ref = useRef(initialValue);

  • An initial value can be of any type.
  • It returns an object with a single property: { current: initialValue }.
  • The ref.current property is mutable, but it's important not to write or read ref.current during rendering.

With the use of useRef, we can achieve the same result as document.getElementById('myInput'), but in a more React-friendly way.

const myRef = useRef(null);   // { current: null }
<input ref={myRef} />
Enter fullscreen mode Exit fullscreen mode

By declaring const myRef = useRef(null);, we create a useRef object with an initial value of null.
When we use <input ref={myRef} /> in our React component, React creates a DOM node for the <input> element. React then assigns a reference to this node to myRef.current.

Since myRef.current now holds a reference to the actual DOM node of the <input> element, you can perform operations or access properties and methods of the DOM node through the myRef.current object. This allows you to manipulate the DOM directly, if necessary, within a React component.

  const inputRef = useRef(null);

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

      console.log("inputRef.current", inputRef.current);
  }

  return (
    <>
      <input ref={inputRef} placeholder="Write your name" />
      <button onClick={handleClick}>Focus the input</button>
    </>
  );
Enter fullscreen mode Exit fullscreen mode

When you inspect the console, you'll get

inputRef.current <input placeholder="Write your name" />
Enter fullscreen mode Exit fullscreen mode

Use cases:
Focus, scroll position, or measuring DOM elements.

Usually, those use cases are used because directly manipulating the DOM can often lead to unexpected behaviors and conflicts with React's internal operations.
As stated in the React documentation, "If you try to modify the DOM manually, you can risk conflicting with the changes React is making."

The code below is from the React Documentation.

React Doc

import { useState, useRef } from 'react';

export default function Counter() {
  const [show, setShow] = useState(true);
  const ref = useRef(null);

  return (
    <div>
      <button
        onClick={() => {
          setShow(!show);
        }}>
        Toggle with setState
      </button>
      <button
        onClick={() => {
          ref.current.remove();
        }}>
        Remove from the DOM
      </button>
      {show && <p ref={ref}>Hello world</p>}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

"After you’ve manually removed the DOM element, trying to use setState to show it again will lead to a crash. This is because you’ve changed the DOM, and React doesn’t know how to continue managing it correctly."

In general, when working with DOM elements in React, it is recommended to use useRef instead of document.getElementById.

  1. React component encapsulation:
    React follows a component-based architecture, where each component is self-contained and manages its own state. Directly accessing elements using document.getElementById breaks this encapsulation principle by bypassing the component's internal logic and accessing elements externally.

  2. React's virtual DOM:
    As mentioned above, it's important not to write or read ref.current during rendering.
    React uses a virtual DOM to efficiently update and reconcile changes in the actual DOM. Directly manipulating the DOM using document.getElementById can bypass React's virtual DOM and cause inconsistencies, leading to unexpected behavior or rendering issues.

  3. Component lifecycle and re-renders:
    React components can go through various lifecycle phases, including mounting, updating, and unmounting. useRef maintains a consistent reference across these lifecycle phases In contrast, document.getElementById would require querying the DOM every time you need to access the element, which can be less efficient.

DOM manipulation must occur at the appropriate time because of the React component lifecycle.
When the component renders, the ref value may not have been updated yet. If you try to read or modify the ref value directly during rendering, it might lead to incorrect or stale data. ("Do not write or read ref.current during rendering.")

To avoid the conflict, we use useEffect.

import React, { useRef, useEffect } from 'react'; 

function MyComponent() { 
    const myRef = useRef(null); 

    useEffect(() => { 

        if (myRef.current) { 
            myRef.current.focus(); 
        } 
    }, []); 

return <input ref={myRef} />;
}
Enter fullscreen mode Exit fullscreen mode

By using useEffect appropriately, you can ensure that your DOM manipulations align with React's rendering and reconciliation process, which helps maintain the integrity of the virtual DOM and avoids inconsistencies or unexpected behavior.

While useEffect can address the issues related to React's virtual DOM, its main role is to handle side effects (e.g., operations like DOM manipulation, API calls, event listeners) rather than directly affecting how React manages the virtual DOM.

According to the document, it is possible to modify the DOM, but it is advisable to use it with caution and consider using DOM manipulation primarily for non-destructive actions.

Top comments (0)