React useRef can be used for more than you think


Nov 10 · 4 min read

Image for post

Photo by Tatiana Rodriguez on Unsplash

You have probably used the useRef hook to access DOM nodes.

If you search for articles on ref, this is the most common example you’d find

import React, { Component, createRef } from "react";                                               class CustomTextInput extends Component {                              textInput = createRef();                                                      focusTextInput = () => this.textInput.current.focus();                                                  render() {                          
return (
<input type="text" ref={this.textInput} /> <button onClick={this.focusTextInput}>Focus the text input</button> </>

The above example shows how you’ll use refs if you have a class component.

If you are using functional components, this is the approach you’ll take to achieve the same thing,

function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
return (
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>

Another common usage of the useRef hook is when you want to grab the input in forms, like this,

function Nameform {

const inputEl = useRef(null);

const handleSubmit=(event)=> {
alert('A name was submitted: ' + inputEl.current.value);


render() {
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={inputEl} /> </label>
<input type="submit" value="Submit" />

But, the same property of refs can be used for more than just storing DOM references.

I’m going to provide two code examples which will demonstrate how refs can be used, which may help in understanding refs better.

Imagine a scenario where we need to log something to console at a particular interval when a particular component is mounted. Let me do this in a class component first,

class App extends React.Component{constructor(props){super();this.interval=null;this.state=true;}componentDidMount(){this.interval=setInterval(()=>{console.log("This is a log");},2000)}handleCancel=()=>{clearInterval(this.interval);}handleToggle=()=>{this.setState(()=>{return !this.state});}render() {console.log("App rendered");return <><h1>Hello</h1><button onClick={this.handleCancel}>Cancel Timer</button><button onClick={this.handleToggle}>Toggle State</button></>;}}

We store the timer in an instance variable. We could also have stored the timer in the state but it would cause an extra render. We don’t want that.

Now, if we wanted to this in a functional component, how would you store the timer? We don’t have instance variables at our disposal in functional components.

useRef to the rescue

function App() {console.log("App rendered");const [state, toggle] = useState(true);const intervalRef = useRef();useEffect(() => { const id = setInterval(() => {   console.log("This is a log")  },2000); intervalRef.current = id; return () => {  clearInterval(intervalRef.current); };},[]);function handleClick(){ clearInterval(intervalRef.current);}return ( <div> <button onClick={handleClick}>Cancel Timer</button> <button onClick={()=>{ toggle(!state)}}>Toggle</button> </div>

Here, intervalRef acts just like an instance variable.

If you’re thinking why we had to use a ref and not just a local variable, remember that there are no instances of functional components, so on the next re render, the previous data associated with the function is lost.

Although, I should mention that you could probably get away with doing something like this,

let interval=null;
function App(){
// ...

function handleClick(){

clearInterval(interval); }}

Which is, storing the timer reference in a variable declared outside the functional component.

But, this is obviously not a good solution and not recommended. Because, well, for starters, this will break if you have the same component rendered more than once.

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component — React Docs.

Consider a scenario where you need to execute some side effects on a component, but not after the initial render.

In a class component, this is as easy as just using the componentDidUpdate lifecycle method because it is not invoked when the component mounts.

But, in a functional component, we have useEffect for applying side effects and doing something like,

useEffect(() => {
// do something

which will execute on every render, won’t work (because the effect will be executed even after the initial render).

So, once again, useRef to the rescue!

We could do something like this,

const hasMounted= useRef(false)useEffect(() => {
if (hasMounted.current) {
// do something
} else hasMounted.current = true

Why does this work? I think I have already mentioned it in the previous example 🙂

Image for post

Thank you for reading.

Author: Shantun Parmar

Leave a Reply

Your email address will not be published.