From DOM Queries to useRef in React - A Journey of Modern DOM Manipulation

Introduction

In web development, manipulating the Document Object Model (DOM) is a fundamental task. Traditionally, JavaScript developers have relied on methods like document.getElementById or document.getElementByClass to access and interact with specific elements on a webpage. However, with the rise of React and its component-based architecture, a new approach has emerged: the useRef hook.

In this article, we'll explore how useRef revolutionizes DOM manipulation in React, drawing analogies from traditional JavaScript practices.

Understanding Traditional DOM Manipulation

Imagine a bustling library, filled with countless books arranged neatly on shelves. Each book represents an element on a webpage, whether it's a button, input field, or a paragraph. In traditional JavaScript, accessing a specific book (element) in the library involves meticulously searching through the shelves using methods like document.getElementById or document.getElementByClass.

In this scenario, document.getElementById acts as the librarian, helping us locate the desired book (element) by its unique ID.

DOM Manipulation in React

When you're building something more complex, like a dynamic web application with React, things can get a bit tricky. React has its own way of managing the elements on the page, called the Virtual DOM. It's like a blueprint of the actual DOM, helping React efficiently update only the parts of the page that need to change.

Here's where useRef steps in as a helpful tool. A powerful hook that bridges the gap between React components and DOM manipulation.

Analogously, useRef serves as a librarian within each React component, providing a way to reference specific elements directly.

You should know useRef features:

  1. Rendering: Does not trigger re-renders when the value changes.

  2. Return Type: .current property to access the mutable value.

  3. Persistence: Value persists across renders of the component.

  4. Mutable: Allows for mutable values that don't cause re-renders.

  5. Usage: Often used for storing references to DOM elements or mutable values.

  6. Initialization: Can be initialized with an initial value.

  7. Component Scope: Scoped to the component where it's declared.

  8. Optimization: Useful for optimizing performance by avoiding unnecessary re-renders.

  9. Ref Sharing: Allows sharing mutable values between different hooks or functions within a component.

  10. Example of useRef

In this example:

  • We create a ref called clickCount using useRef(0). This initializes the ref with an initial value of 0.

  • Each time the button is clicked, the handleClick function is called.

  • Inside handleClick, we increment the clickCount.current value by 1 to keep track of the number of clicks.

  • We then log the current click count to the console.

This way, we're able to maintain a count of button clicks across renders without causing the component to re-render. It's a simple and effective use of useRef for storing mutable values.

How values are stored in useRef

In React, a ref is like a special container that can hold a value. Imagine it as a box with a label on it. This box can hold different things, like a reference to a DOM element, a number, a string, or anything else.

When you create a ref using useRef, React gives you this box and attaches a special property called current to it. This current property is where you put the thing you want to store in the box.

const refStoreBox.current = useRef('initial_value')

Now, the interesting part is that this box (refStoreBox) sticks around even when your component re-renders. So, if you put something in the box during one render, it will still be there during the next render, unless you decide to change it.

This is super useful because it allows you to keep track of things between renders without causing your component to re-render unnecessarily. For example, you can use a ref to store a reference to a DOM element, and even if the DOM element changes, the ref will still point to the right thing.

So, in simple terms, a ref in React is like a special box that can hold things. This box sticks around between renders, making it a handy tool for keeping track of things in your components.

Best practices when working with refs

  1. Use Refs Wisely: Refs are helpful for accessing things like DOM elements or browser features that React doesn't handle directly. But don't rely on them too much. If you're using refs for everything, you might be missing out on the benefits of React's component-based approach.

  2. Avoid Using Refs During Render: Refs shouldn't be accessed or changed while React is rendering your components. If you do, it can cause unexpected behavior. Instead, save ref-related work for later, like in event handlers or useEffect.

  3. Follow Hook Rules: useRef is a hook, so it follows the same rules as other hooks. That means you should always call useRef at the top level of your component, and avoid using hooks inside loops or conditionals. It's okay to change the ref's value inside those blocks, though.

Forward Ref

Imagine you have a parent component and a child component in your React app. Sometimes, you want the parent to be able to do something with an element or component inside the child, like focusing on an input field or scrolling to a certain part of a list.

That's where forwardRef comes in. It's like a special tool that allows the parent to send a ref down to the child, even if the child is just a simple function. This way, the parent can still grab hold of things inside the child and do whatever it needs to do with them.

So, forwardRef is like a bridge that helps refs travel from the parent to the child, making it easier for components to work together and share information.

In this example:

  • We create a TextInput component using forwardRef, which allows us to pass a ref from the parent to the input element inside TextInput.

  • In the ParentComponent, we create a ref using useRef called inputRef.

  • When the button is clicked, the focusInput function is called, which focuses on the input element using inputRef.current.focus().

  • We pass the inputRef to the TextInput component, allowing the ParentComponent to control the focus of the input field inside TextInput.

When not to use useRef()

  1. Declarative Rendering:

    • If you can achieve your goal using React's declarative approach, where you describe what you want to happen rather than how to do it, then it's better to avoid using useRef.

    • For example, instead of manually manipulating a DOM element with a ref, you can often achieve the same result by updating state and letting React re-render the component accordingly.

  2. State Updates Triggering Re-renders:

    • If you need your component to update or re-render based on changes in state, avoid using useRef.

    • Since updating a ref doesn't trigger a re-render, using useRef in scenarios where state changes should update the UI can lead to inconsistencies.

    • Example: Suppose you have a counter component that displays the count value. If you update the count value using a ref instead of state, the UI won't reflect the updated count.

Summary

When you want a component to “remember” some information, but you don’t want that information to trigger new renders, you can use a Ref.

  • A ref is a plain JavaScript object with a single property called current, which you can read or set.

  • You can point a ref to any value. However, the most common use case for a ref is to access a DOM element.

  • You can ask React to give you a ref by calling the useRef Hook.

  • Like state, refs let you retain information between re-renders of a component.

  • Unlike state, setting the ref’s current value does not trigger a re-render.

  • Don’t read or write ref.current during rendering. This makes your component hard to predict.