React as a frontend framework encourages a reactive programming model where changes in state lead to component re-rendering. This is what allows React to efficiently keep the UI in sync with changes to underlying data.
However, there are scenarios in which you may be required to store and access values that should not trigger re-renders when they change. It is in such instances that you require the useRef() hook.
What is the useRef() hook?
In this blog, I will answer this question and provide some insight into the best use cases and common pitfalls when working with the useRef().
Introduction
The useRef is a react hook that takes an ‘initial value’ as its argument and returns a mutable ref object whosecurrent property is initialized to the passed argument. The 'initial value' can be of any type. The returned ref object is similar to a normal JavaScript object.
The ref object persists for the full lifetime of the component and can be used to reference a value that's not needed for rendering. This allows the persisting of values between renders. Thus, refs are perfect for storing information that doesn't affect the visual output of your component.
Ref objects are useful when you need to access and manipulate elements or components outside the regular React component lifecycle, such as focusing an input element, accessing component methods, or managing state across renders.
Syntax
To initialize the useRef hook you begin by importing it at the top level like other react hooks:
import { useRef } from 'react';
After that, you can use it in your functional components:
const MyComp = () => {
//initialize a new ref whose object has the value null
const myRef = useRef(null)
//Change the value of the current property in the myRef object
myRef.current = 'Hello'
}
In the code above, we create a new useRef object with a value of null and then proceed to update its value using the current property. It should be noted that changing the value of this ref object doesn't trigger a re-render.
What is the purpose of the useRef()?
1. Creating references
One of the primary uses of the useRef is to create references to DOM elements as well as to a React component. These references allow you to interact with and manipulate these elements or components in a controlled and declarative way.
Let's have a look at each one:
a. Using the useRef() to reference DOM elements.
The fact that DOM elements are also objects means that you can reference them using the useRef. React has built-in support for this. To undertake this:
- First, import the useRef function from React and create a new ref with an initial value of null:
import { useRef } from 'react';
const myComponent = () => {
const myRef = useRef(null);
//...
}
- Next, you attach the myRef ref to the desired DOM element, say, in this case, input, by assigning it to the ref attribute within your JSX.
//..
return (
<input ref = {myRef} />
)
- With the ref in place, you can access and manipulate the input element and perform actions such as retrieve properties, measure dimensions, or modify styles.
For example, you can invoke the focus() method on the input element by pressing a button, as shown in the following better-organized code:
import { useRef } from 'react';
const MyComponent = () => {
const myRef = useRef(null);
const handleClick = () => {
myRef.current.focus();
}
return (
<div>
<input ref={ myRef } />
<button onClick={handleClick}>
Focus
</button>
</div>
);
}
Some applicable use cases where referencing the DOM using useRef may come in handy are:
Dynamic Styling: Adjusting styles and classes based on user interactions becomes straightforward with direct access to DOM elements.
Dimension Measurements: You can measure the dimensions of elements, allowing for responsive design and layout adjustments.
Validation and Feedback: Validate user inputs and provide feedback by interacting with the DOM.
Smooth Animations: Effortlessly trigger animations and transitions by changing element properties using refs.
b. Using the useRef() to reference React components.
The useRef() hook provides a way for directly accessing and interacting with a React component from another component (kind of like a more direct, component-to-component communication).
You can use it to call methods, access properties or manage component state without involving the component's parent.
While it is possible to use the useRef to access a child component’s methods and properties, it is generally not recommended as it goes against the principles of React’s uni-directional data flow. If you need to manage a child component’s state from a parent component, it is recommended you apply props
.
The useRef is most suitable when you need to reference and interact with a specific instance of a component, particularly in managing behavior.
A good scenario is when you need to call a child component method from the parent component. Props cannot be used in this instance since they only work one way, from parent to child.
To do this though you also need the forwardRef function in React which allows you to forward a ref from a parent component to one of its child components. forwardRef allows you to maintain encapsulation in the child's component's implementation while still allowing the parent component to reference it.
Here’s a code example:
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
const MyChildComp = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
greeting: () => {
alert("Greetings from the Child component");
}
}));
return (
<div>Child component here!!!</div>
);
});
const App = () => {
const myRef = useRef();
const handleClick = () => {
myRef.current.greeting();
};
return (
<div>
<button onClick={handleClick}>Click</button>
<MyChildComp ref={myRef} />
</div>
);
}
export default App;
A brief breakdown:
We define a myRef ref, which will be used to create a reference to the child component (MyChildComp) in the App component.
The MyChildComp is a child component created with the forwardRef function allowing it to accept a ref attribute.
Inside MyChildComp, we use the useImperativeHandle hook to define a function, greeting, that can be called from the parent component. The purpose of this function is to display an alert with the message "Greetings from the Child component."
The handleClick function is also defined. When the "Click" button is clicked, it calls myRef.current.greeting(), which in turn triggers the greeting function in the child component resulting in an alert message being displayed.
The child MyChildComp also renders some simple text: "Child component here!!!"
Output
2. Persisting data through renders
The useRef is also used in storing a value and persisting it across renders of a component. The values are stored in the current property of the useRef object.
Changing these values does not trigger a re-render. Thus, the useRef is a suitable way for storing values that shouldn't affect the component's UI but need to be reserved.
It is therefore appropriate to use in instances such as when caching data, managing previous state, or in form input validations.
For a better understanding, here is an example:
import React, { useRef, useEffect } from 'react';
const Previous = () => {
const previousValue = useRef(null);
const [value, setValue] = useState('Hello')
useEffect(() => {
previousValue.current = value
}, [value])
return (
<div>
<p>Previous value: {previousValue.current}</p>
<p>Current value: {value}</p>
</div>
)
}
We initialize a previousValue ref and set its initial value to null. We also create a state variable value using useState and initialize it with the string 'Hello'.
In the useEffect, you update the previousValue.current with the current value of the value state variable whenever it changes. This explains why we have it set as a dependency in the useEffect.
The UI displays both the previous value and the current one. When the value state changes, the useEffect updates previousValue.current, allowing you to display both the previous and current values in the UI.
Common Pitfalls when using the useRef hook.
The useRef() hook is a useful hook as described above, but it also has some potential pitfalls that you should be aware of.
Some of them include:
Not triggering a re-render: Unlike useState, useRef does not cause a re-render when its value changes. This can be useful for performance reasons, but it can also lead to stale or inconsistent UI if you rely on the refvalue for rendering. To avoid this, you should always use useState or useEffect to update the UI when the ref value changes.
Not Using It for DOM Manipulation: useRef is often associated with DOM manipulation as shown earlier, precisely when referencing DOM elements. However, it's important to ensure that you're using it appropriately and not modifying the DOM directly outside of React's virtual DOM.
Using refs for derived state: Refs are meant to store values that are not directly related to the UI state, such as DOM nodes, timers, or subscriptions. However, sometimes you may be tempted to use refs for values that are derived from props or state, such as computed values or previous values. This can lead to bugs and confusion, as refs do not participate in the React data flow and do not update automatically when props or state change. To avoid this, you should use useMemo or usePrevious hooks instead of refs for derived state.
Using refs for anti-patterns: Refs can also be used for anti-patterns that violate the React principles of declarative programming and unidirectional data flow. For example, you may use refs to store global variables, manipulate props or state directly, or call child component methods imperatively. These practices can make your code harder to understand, test, and maintain. To avoid this, you should follow the React best practices and use hooks like useContext, useReducer, or custom hooks instead of refs for these scenarios.
Conclusion
The useRef as seen in this blog is best suited for when you need to persist values across renders and also when you need to create references.
It is a rather peculiar React hook in that for effective use, you as the programmer are required to fully understand when and where to use it for the best outcome in your program while keeping in mind the expected React programming principles to avoid pitfalls (tiny crazy incidents😞).
I hope this blog helps in building that understanding. Happy coding!
Top comments (0)