Refs in React provide a way to interact with the DOM directly, offering a powerful tool for scenarios that go beyond the typical React component lifecycle. In this comprehensive guide, we’ll start with the basics of React refs and then delve into a real-world example to illustrate their practical application.React refs are used for several reasons, playing a crucial role in managing and interacting with DOM elements or React elements directly. Here are the main reasons why refs are needed in React:
- Accessing DOM Elements: Refs provide a way to directly access a DOM node in the React component. This is crucial for managing focus, text selection, or media playback, which are difficult or impossible to do with React’s declarative API alone.
- Integrating with Third-Party DOM Libraries: Many third-party libraries interact directly with the DOM. Refs make it possible to give these libraries access to DOM nodes created by React so they can manage those elements as needed.
- Triggering Imperative Animations: While React is primarily declarative, there are times when you need to trigger animations or transitions imperatively. Refs allow you to directly manipulate DOM nodes for animation purposes.
- Managing Focus, Text Selection, or Media Playback: Certain UI operations, such as managing focus or text selection, are inherently imperative and require direct access to DOM elements. Refs provide a straightforward way to achieve these tasks in React components.
- Avoiding Unnecessary Renders: Since refs do not trigger re-renders when their content changes, they can be used to store values that you want to keep track of without causing the component to re-render. This can be useful for values that are updated frequently or do not impact the render output.
- Measuring DOM Nodes: If you need to measure the dimensions or position of an element, refs are necessary. You can access the DOM node directly to use methods like getBoundingClientRect or offsetHeight to get the information you need.
Refs should be used sparingly because they break the usual flow of React’s declarative model. React’s documentation suggests using refs as a last resort when there is no other way to accomplish your goal with React’s state and props system. React also provides a few different ways to create refs, including createRef (for class components) and useRef (for function components), to accommodate different use cases and component types.
React refs are a feature that allows developers to access and interact with DOM elements or React components directly. They provide a way to reference an element or component instance outside the regular data flow (props and state) of React components. This direct access is necessary for several use cases where the declarative nature of React does not offer a straightforward solution. Here’s a deeper look into what React refs are and how they work:
Types of Refs
- Callback Refs: Before React 16.3, callback refs were more commonly used. You provide a function that takes the DOM element or component instance as its argument. This function then does something with that reference, such as storing it in the component’s property for later use.
- createRef: Introduced in React 16.3, createRef creates a ref object that can be attached to React elements via the ref attribute. The current property of this object then holds the reference to the DOM element or class component instance.
- useRef: A hook introduced for functional components in React 16.8. It creates a mutable ref object whose .current property is initialized to the passed argument. The object persists for the lifetime of the component.
How Refs Work
- Accessing DOM Elements: You can attach a ref to a DOM element in the render method. This allows you to directly interact with that element, bypassing the React layer. For example, you might use a ref to focus on an input element immediately after a component mounts.
- Accessing Child Components: Refs can also be used to interact with child components, allowing parent components to call methods on the child. This is particularly useful when you need to access a method defined in a child component that manipulates its internal state or performs some imperative action.
- Usage with Function Components: Since function components do not have instances, refs can only be used with them for accessing DOM elements unless you use forwardRef to forward a ref to a DOM component within the function component.
Best Practices and Cautions
- Avoid Overuse: Refs should be used sparingly as they escape the declarative paradigm of React. When you use a ref, you’re stepping outside React’s normal data flow and should do so with caution.
- Not for Data Flow: Refs are not part of React’s reactive data flow and updates to refs do not cause a component re-render. They are meant for more direct manipulation or accessing of elements and components.
- Use for Imperative Actions: They are best used for actions that require direct manipulation of the DOM, such as focus management, text selection, or imperative animations.
Refs provide an escape hatch out of React’s declarative paradigm, allowing for more direct interaction with elements when necessary. However, the need for refs is relatively rare, and they should be used judiciously to keep components declarative and maintainable.
Creating Refs with useRef
Refs are created using the React.createRef() function. This function returns a mutable object with a current property pointing to the DOM element.
Using a React ref to store values without causing a re-render is a useful technique, especially when you need to track values that change over time but do not directly influence the UI. The useRef hook in functional components is commonly used for this purpose. Here’s a simple example to demonstrate how you can use a ref to store a counter value that updates on a button click, without causing the component to re-render for each update.
Example 1: Tracking Clicks without Re-render
This example involves a functional component that tracks the number of button clicks. It displays a count of renders (which only increases when the component itself re-renders for reasons other than the button click) and the count of button clicks (tracked with a ref and not causing re-renders).
import React, { useState, useRef } from ‘react’;
function ClickTracker() {
// State to force re-render
const [renderCount, setRenderCount] = useState(0);
// Ref to track button clicks without causing re-renders
const clickCountRef = useRef(0);
const handleButtonClick = () => {
clickCountRef.current += 1;
console.log(`Button was clicked ${clickCountRef.current} times`);
};
const forceRender = () => {
setRenderCount((prevRenderCount) => prevRenderCount + 1);
};
return (
<div>
<button onClick={handleButtonClick}>Click me</button>
<button onClick={forceRender}>Force re-render</button>
<p>Component render count: {renderCount}</p>
<p>Button click count (check console for updates)</p>
</div>
);
}
export default ClickTracker;
How It Works
- clickCountRef: This ref is used to keep track of the button clicks. The .current property is mutated directly inside handleButtonClick, which updates the click count without causing the component to re-render.
- handleButtonClick: This function increments the click count stored in clickCountRef and logs the current count to the console. Despite the click count changing, this action does not trigger a re-render of the component.
- forceRender: This function increments the renderCount state, causing the component to re-render. This illustrates that re-renders are controlled and not triggered by changes to refs.
- Display and Console Log: The UI displays the render count, which only increases when forceRender is clicked. The click count does not affect the UI directly and is only logged to the console to demonstrate that it’s being tracked.
This pattern is particularly useful when you need to keep track of values that are relevant for logic or actions within your component but do not need to be displayed or cause updates to the UI.
Example 2 : Focus Input on Mount
import React, { useEffect, useRef } from ‘react’;
const FocusInputOnMount = () => {
const inputRef = useRef(null);
useEffect(() => {
// Set focus on the input element when the component mounts
inputRef.current.focus();
}, []);
return <input ref={inputRef} placeholder="Start typing…" />;
};
export default FocusInputOnMount;
In this example, useRef is used to create a ref (inputRef), which is then attached to an input element. The useEffect hook ensures that focus is set on the input when the component mounts.
Example 3 : AutoFocus Input Form based on max length of input
Let’s move on to a more practical example that simulates a common form interaction. In this scenario, users should seamlessly move between input fields after entering a certain number of characters. This can be achieved using refs to manage focus dynamically.
import React, { useRef, useState, useEffect } from ‘react’;
const AutoFocusInputForm = () => {
const firstInputRef = useRef(null);
const secondInputRef = useRef(null);
const thirdInputRef = useRef(null);
const [firstInput, setFirstInput] = useState(”);
const [secondInput, setSecondInput] = useState(”);
const [thirdInput, setThirdInput] = useState(”);
const handleInputChange = (input, setInput, nextInputRef, maxLength) => {
setInput(input);
if (input.length === maxLength && nextInputRef) {
nextInputRef.current.focus();
}
};
useEffect(() => {
firstInputRef.current.focus();
}, []);
return (
<div>
<h2>AutoFocus Input Form</h2>
<form>
<label>
First Input:
<input
ref={firstInputRef}
type="text"
value={firstInput}
onChange={(e) => handleInputChange(e.target.value, setFirstInput, secondInputRef, 3)}
maxLength={3}
/>
</label>
<br />
<label>
Second Input:
<input
ref={secondInputRef}
type="text"
value={secondInput}
onChange={(e) => handleInputChange(e.target.value, setSecondInput, thirdInputRef, 4)}
maxLength={4}
/>
</label>
<br />
<label>
Third Input:
<input
ref={thirdInputRef}
type="text"
value={thirdInput}
onChange={(e) => handleInputChange(e.target.value, setThirdInput, null, 5)}
maxLength={5}
/>
</label>
</form>
</div>
);
};
export default AutoFocusInputForm;
In this example, the useRef hook is utilized to create refs for each input field (firstInputRef, secondInputRef, and thirdInputRef). The handleInputChange function dynamically moves focus to the next input field after a certain number of characters are entered.
Conclusion
In summary, while React refs provide a powerful escape hatch for certain scenarios, they should be used thoughtfully, adhering to React’s declarative principles whenever possible. Understanding when and how to leverage refs can greatly enhance the capabilities of a React application.