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:
Rendering: Does not trigger re-renders when the value changes.
Return Type:
.current
property to access the mutable value.Persistence: Value persists across renders of the component.
Mutable: Allows for mutable values that don't cause re-renders.
Usage: Often used for storing references to DOM elements or mutable values.
Initialization: Can be initialized with an initial value.
Component Scope: Scoped to the component where it's declared.
Optimization: Useful for optimizing performance by avoiding unnecessary re-renders.
Ref Sharing: Allows sharing mutable values between different hooks or functions within a component.
Example of useRef
In this example:
We create a ref called
clickCount
usinguseRef(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 theclickCount.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
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.
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.
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 usingforwardRef
, which allows us to pass a ref from the parent to theinput
element insideTextInput
.In the
ParentComponent
, we create a ref usinguseRef
calledinputRef
.When the button is clicked, the
focusInput
function is called, which focuses on the input element usinginputRef.current.focus()
.We pass the
inputRef
to theTextInput
component, allowing theParentComponent
to control the focus of the input field insideTextInput
.
When not to use useRef()
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.
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.