Hooks: Why You Need to Know Them?

Before diving into useRef, let’s clear the air on hooks.

Hooks are functions that we use to handle things like data (state), actions that run after the component shows up (like fetching data), and directly working with DOM elements.

What’s the point, though?

Hooks simplify your components. You don’t need to extend React.Component or juggle lifecycles. Instead, you call functions like useState, useEffect, and yes, useRef, to get things done.

When do you use them?

Whenever you’re dealing with stateful logic, side effects, or need to interact with the DOM (spoiler alert: that’s where useRef shines).

The Problem We’re Solving

Picture this: You’re building a form, and you want to focus on an input when the user clicks a button. Or maybe you’ve got an expensive DOM element that you don’t want to rerender needlessly. That’s when you reach for useRef.

So, What Is useRef?

Technically, useRef holds a reference to an HTML element. useRef is a hook that gives you a persistent, mutable value that sticks around between renders. React technically keeps this thing alive no matter how many times I rerender.

Syntax

const h1Ref = useRef<HTMLHeadingElement>(null);

A Simple Example

import {useEffect, useRef, type ReactNode} from 'react';

export default function UseRefDemo() {
    const h1Ref = useRef<HTMLHeadingElement>(null);

    useEffect(() => {
        console.log("with useRef:", h1Ref.current?.textContent)
    }, [])

    return (
        <h1 ref={h1Ref}>This is h1 data!</h1>
    )
};

What’s useEffect doing?

Two Core Uses of useRef

1. Accessing DOM Elements Directly

In the following example, we’re using useRef to grab references to the <input> and <button> elements. When the page loads, the button is automatically focused. When you click the button, it focuses the input.

import {useRef, useEffect} from 'react';

export default function(){
    const inputRef = useRef<HTMLInputElement>(null);
    const buttonRef = useRef<HTMLButtonElement>(null);

    function clickHandler(){
        console.log(inputRef.current?.placeholder);
        inputRef.current?.focus();
    }

    useEffect(() => {
        buttonRef.current?.focus();}, 
        []);

    return (
        <>
            <input placeholder="Click button to focus on me" ref={inputRef}/>
            <button onClick={clickHandler} ref={buttonRef}>click me!</button>
        </>
    );
} 

That’s why inputRef.current?.focus() works, it’s talking straight to the real <input> element in the DOM. No rerenders or state updates needed.

inputRef.current?.focus()

2. Storing a Mutable Value

Sometimes, in a React component, you want to store a value that doesn’t get reset when the component re-renders. For example, you might want to:

import { useRef, useState } from "react";

export default function () {
  const [show, setShow] = useState(true);
  const countRef = useRef(0);
  let num = 0;

  function clickHandler() {
    countRef.current += 1;
    num += 1;
    console.log("Ref:", countRef.current, "Plain:", num);
  }

  return (
    <>
      <button onClick={clickHandler}>Click me</button>
      <button onClick={() => setShow(!show)}>Force Re-render</button>
    </>
  );
}

This example shows how useRef helps hold a value across re-renders, while a plain variable (let num) does not.

Very Important Node: Avoid overusing it.

If you find yourself using refs for data that’s supposed to update the UI, you’re probably using it wrong. Reach for useState instead.