Back

Build a React Timer Application with useRef

Build a React Timer Application with useRef

I need to warn you on this: if beginners learn that elements can be easily accessed by useRef, vanilla Javascript will be the day’s order, which defiles the knowledge of state management we have learned forehand in React. As tricky as it looks, it’s that powerful. UseRef should only be used to create a reference in our component if need be. We will dive into this in the article; you will learn some everyday use of the useRef React hook, and we will also build a stopwatch timer application with it.

The goal of this article

Development area

You’ll need React 18 and Node installed to follow through. If you want to install React 18, follow the steps below:

npm install react@18 react-dom@18

To take advantage of all the new features of React 18 you will need to go to your entry file, typically the index.js, and replace everything with this:

import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';

const root = createRoot(document.getElementById("root"));
root.render(<App/>);

Why useRef, and how to use it?

React state has a few limitations, and that’s where React references come in handy. useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the entire lifetime of the component.

One advantage useRef has over useState could be well stated as “the reference does not trigger a re-render, but in state, when state is updated it triggers a re-render.

It’s key to note that we can access anything in our component and set focus to an element. As it has been said forehand, I will go ahead and illustrate how to do that, but before then, I would like to point out two essential take-home keys for the useRef hook:

  • The value persists.
  • Updating the reference does not trip on a re-render.

What exactly do we need with these keys? I will take an example of what I would like to do, to show how many times a component is rendered on the screen. A quick fix is to use the useState hook, but it will leave us with an infinite loop; let me show you.

import React, { useState, useEffect } from 'react';
function Render() {
  
  const [render, setrender]=useState();
  useEffect(()=>{
    setrender(prevrender=>prevrender+1)
  })
  console.log(render);
  
  return (
    <>
      <div>
        This component rendered= {render} times
      </div>
    </>
  
  )
};

The code up there will lead you to an infinite loop because the state isn’t the best option for things like this. If the state is not, what is? A quick fix is to use Refs.

Ref is similar to state in that it persists between renders of your component, but it doesn’t cause your component to re-update when a change occurs, as earlier stated. Let’s go ahead and fix the above logic using useRef.

    const render = useRef(0);
    // current {0}

Above is the useRef syntax. It returns an object by default which is the current property, which also by default sets itself to 0. Whenever the current property is updated, it persists between our various renders.

import React, { useRef, useEffect, useState } from 'react';
function Render() {
  const [age, setAge] = useState(20);
  const render = useRef(0)
  
  useEffect(() => {
    render.current = render.current + 1
  })
  function updateAge() {
    setAge(prevage => prevage + 1)
  }
  
  return (
    <>
      <div className='center'>
        <p>This site is strictly for adults ranging from 20-...</p>
        <p>you are {age} years old</p>
        <button onClick={updateAge}>set age</button>
        <p>This state rendered {render.current} times</p>
      </div>
  
    </>
  
  )
};
  
export default Render;

A problem in React

The code up there is a simple logic for registration. Unlike using the use state hook to indicate how many times a component is rendered, using useRef doesn’t send us into an infinite loop, and if we change updateAge to increment by five or whatever, it will never cause our component to re-render.

    function updateAge() {
    setAge(prevage => prevage + 5)
    }

increments by 5

Let’s go ahead and create an input with a bit of styling and set up a reference. These are the steps we will follow to set focus;

  1. We need an input field to be able to return the focus to it.
  2. We need to define the reference.
  3. We need a button to be able to return focus to the input element in our component.
  4. We need to add the reference attribute to our element.
import React, { useState, useRef } from 'react';

import './App.css';
  
function App() {
  const [insert, setInsert] = useState('');
  //const render = useRef()
  
  const handleOnchange = (e) => {
    setInsert(e.target.value);
  }
  
  return (
    <div className="App">
      <input
        type="text"
        placeholder='use reference'
        value={insert}
        onChange={handleOnchange}
        className="input"
      />
      <br /><br />
    
      <div> <p className='text'>{insert}</p></div>
    </div>
  );
}
  
export default App;

Some styles:

.input {
      border-radius: 10px;
      padding: 10px;
      margin: 20%;
  }

  .body {
      background-color: burlywood;
      display: flex;
      justify-content: center;
      align-items: center;
  }

  .text {
      color: white;
      margin: 0;
  }

We can now go ahead and set up a reference for it. First, by importing the useRef hook and defining our reference, we can name our reference reference.

import React, { useState, useRef } from 'react';<-//FOCUS HERE

import './App.css';
  
function App() {
  const [insert, setInsert] = useState('');
  const reference = useRef() <-//FOCUS HERE

Then, we need to create the function that enables us to set focus on the input;

const returnCursor = () => { 
  reference.current.focus()
}

We also need to pass the above code to our JSX. How do we do that? We need to create a button and pass the function into it, so whenever we click the button, it runs the function return cursor. Let’s do that.

<div className='button'>
  <input
          type="button"
          value="Return cursor"
          onClick={returnCursor}
        />
      </div>

//a little styling
.button {
        border-radius: 10%;
        padding: 10%;
        color: black;
    }

Lastly, we need to set attributes in our input element;

ref={reference}

We have just emphasized setting focus for some time now, but then, what exactly are we setting focus to? Whenever the button is clicked, it’s meant to trigger the input element, and when the element is triggered, the text cursor re-appears.

Moving the cursor with useRef

The above video illustrates what we have been able to do with useRef.

Open Source Session Replay

OpenReplay is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data.

replayer.png

Start enjoying your debugging experience - start using OpenReplay for free.

UseRef to build a stopwatch timer

At the beginning of the tutorial, we saw a video; let’s build it as we have learned the basics of useRef, which can enable everybody to stand out using this hook.

import React, { useState, useRef } from 'react';

import './App.css';
  
  
function App() {
  const [insert, setInsert] = useState('');
  const [secs, setSecs] = useState(0);
  const reference = useRef(0);
  const time = useRef();
  
  const handleOnchange = (e) => {
    setInsert(e.target.value);
    reference.current++;
  }
  
  const startTime = () => {
    time.current = setInterval(() => {
      reference.current++;
      setSecs(prev => prev + 1)
    }, 1000)
  }
  
  const stopTime = () => {
    clearInterval(time.current);
    time.current = 0;
  }
  
  const resetTime = () => {
    stopTime();
    if (secs) {
      reference.current++;
      setSecs(0);
    }
  }
  return (
    <div className='App'>
      <input
        type="text"
        placeholder='use reference'
        value={insert}
        onChange={handleOnchange}
        className="input"
      />
      <br /><br />
      <div className='text'> <p className='text'>Please input your name: {insert}</p></div>
      <div >
      </div>
      <input type="button" value="start time" onClick={startTime} />
      <input type="button" value="stop time" onClick={stopTime} />
      <input type="button" value="reset time" onClick={resetTime} />
      <p>Timer{secs}</p>
      <div>
        <p>Render:[{reference.current}]</p>
      </div>
    </div>
  );
}
  
export default App;

Styling:

.input {
    padding: 10px;
    border-radius: 20px;
    width: 70em;
}
  
.button {
    padding: 20px;
    border-radius: 10px;
    display: flex;
    justify-content: center;
    align-items: center;
}
  
.body {
    display: flex;
    justify-content: center;
    align-items: center;
}
  
.text {
    margin: 10px;
}

The above has a stop timer and input. It renders every second and displays the renders and the timer even when we type. Whenever the app re-renders, we do not lose the value because of the use of the useRef hook.

The final result

Conclusion

Regularly updating the state of the React component with the use of the UseState hook can have unintended results. Overall, the take-homes are:

  • We can store values in refs and have them updated, which is more efficient than useState, which can be costly when the values are to be updated multiple times within a second.
  • The returned object will persist for the entire lifetime of the component.
  • It’s key to note that we can access anything in our component and set focus to an element.