React useRef can be used for more than you think

Deb
Deb
Nov 10 · 4 min read
Image for post
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
inputEl.current.focus();
};
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);
event.preventDefault();
}

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

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
Image for post

Thank you for reading.

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store