DEV Community

Cover image for React Mastery Series – Day 14: React Hooks Deep Dive – Understanding useRef and useMemo
Siva Samanthapudi
Siva Samanthapudi

Posted on

React Mastery Series – Day 14: React Hooks Deep Dive – Understanding useRef and useMemo

Welcome back to the React Mastery Series!

In the previous article, we explored useEffect Hook and learned how React handles side effects such as:

  • API calls
  • Timers
  • Event listeners
  • WebSocket connections
  • Cleanup operations

Today, we will explore two more powerful React Hooks:

useRef and useMemo

These Hooks are frequently used in production applications to:

  • Access DOM elements
  • Store values without triggering re-renders
  • Optimize expensive calculations
  • Improve application performance

Understanding useRef Hook

useRef is a React Hook that allows us to store a value that persists across renders without causing the component to re-render.

Syntax:

const reference = useRef(initialValue);
Enter fullscreen mode Exit fullscreen mode

The returned object looks like:

{
  current: initialValue
}
Enter fullscreen mode Exit fullscreen mode

The value is accessed using:

reference.current
Enter fullscreen mode Exit fullscreen mode

useRef vs useState

A common question:

Why do we need useRef when we already have useState?

The difference:

useState useRef
Updates trigger re-render Updates do not trigger re-render
Used for UI data Used for storing values
React tracks changes React does not track changes

Example:

const [count, setCount] = useState(0);
Enter fullscreen mode Exit fullscreen mode

Updating:

setCount(count + 1);
Enter fullscreen mode Exit fullscreen mode

causes:

State Update
      |
      ↓
Component Re-render
Enter fullscreen mode Exit fullscreen mode

With useRef:

const count = useRef(0);
Enter fullscreen mode Exit fullscreen mode

Updating:

count.current++;
Enter fullscreen mode Exit fullscreen mode

does:

Value Updated
      |
      ↓
No Re-render
Enter fullscreen mode Exit fullscreen mode

Using useRef to Access DOM Elements

One of the most common use cases of useRef is accessing DOM elements directly.

Example:

import { useRef } from "react";

function SearchBox() {
  const inputRef = useRef();

  function focusInput() {
    inputRef.current.focus();
  }

  return (
    <div>
      <input ref={inputRef} />

      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Flow:

Button Click
      |
      ↓
focusInput()
      |
      ↓
inputRef.current
      |
      ↓
Input DOM Element
      |
      ↓
focus()
Enter fullscreen mode Exit fullscreen mode

Real-World Example: Login Page

Imagine a banking login page.

When the page loads:

Open Login Page
        |
        ↓
Username Field Automatically Focused
Enter fullscreen mode Exit fullscreen mode

Implementation:

useEffect(()=>{
  usernameRef.current.focus();
},[]);
Enter fullscreen mode Exit fullscreen mode

This improves user experience.


Storing Previous Values Using useRef

useRef can store previous values.

Example:

function Profile({ name }) {
  const previousName = useRef();

  useEffect(() => {
    previousName.current = name;
  }, [name]);

  return (
    <div>
      Current:
      {name}
      <br />
      Previous:
      {previousName.current}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The value survives between renders.


Tracking Render Count

Example:

function App() {
  const renderCount = useRef(0);

  renderCount.current++;

  return (
    <p>
      Render Count:
      {renderCount.current}
    </p>
  );
}
Enter fullscreen mode Exit fullscreen mode

Unlike state, updating this value does not trigger another render.


Storing Timer References

Example:

function Timer() {
  const timerRef = useRef();

  function startTimer() {
    timerRef.current = setInterval(() => {
      console.log("Running");
    }, 1000);
  }

  function stopTimer() {
    clearInterval(timerRef.current);
  }
}

Enter fullscreen mode Exit fullscreen mode

Common use cases:

  • Countdown timers
  • Debounce logic
  • Polling APIs

Real-World Example: WebSocket Connection

In enterprise applications:

Dashboard Open
       |
       ↓
Create WebSocket
       |
       ↓
Store Connection Reference
       |
       ↓
Receive Messages
       |
       ↓
Close Connection
Enter fullscreen mode Exit fullscreen mode

Example:

const socketRef = useRef(null);

useEffect(() => {
  socketRef.current = new WebSocket("wss://example.com");

  return () => {
    socketRef.current.close();
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

Understanding useMemo Hook

Now let's explore:

useMemo

useMemo is used to optimize expensive calculations by memorizing a computed value.

Syntax:

const result = useMemo(
  () => calculation,
  [dependencies]
);
Enter fullscreen mode Exit fullscreen mode

React remembers the result and recalculates only when dependencies change.


Why Do We Need useMemo?

Consider:

function ProductList() {
  const products = calculateProducts();

  return <Product products={products} />;
}
Enter fullscreen mode Exit fullscreen mode

Every render:

Component Render
        |
        ↓
calculateProducts()
        |
        ↓
Heavy Calculation
Enter fullscreen mode Exit fullscreen mode

Even when data has not changed.

This can impact performance.


Using useMemo

Example:

const filteredProducts = useMemo(() => {
  return products.filter((product) => product.price > 1000);
}, [products]);
Enter fullscreen mode Exit fullscreen mode

Now:

products unchanged
        |
        ↓
Use Previous Result


products changed
        |
        ↓
Recalculate
Enter fullscreen mode Exit fullscreen mode

useMemo Example: Expensive Calculation

Without useMemo:

function App() {
  const result = heavyCalculation(value);

  return <h1>{result}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

The calculation runs on every render.


With useMemo:

function App() {
  const result = useMemo(() => {
    return heavyCalculation(value);
  }, [value]);

  return <h1>{result}</h1>;
}

Enter fullscreen mode Exit fullscreen mode

The calculation runs only when value changes.


useMemo with Search Filtering

Real-world example:

const filteredUsers = useMemo(() => {
  return users.filter((user) => user.name.includes(searchText));
}, [users, searchText]);
Enter fullscreen mode Exit fullscreen mode

Used in:

  • User management systems
  • Product search
  • Transaction filtering

useMemo vs useEffect

A common confusion.

useEffect

Used for:

  • API calls
  • Side effects
  • External systems

Example:

useEffect(()=>{
  fetchUsers();
},[]);
Enter fullscreen mode Exit fullscreen mode

useMemo

Used for:

  • Calculating values
  • Performance optimization

Example:

const total = useMemo(()=>{
  return calculateTotal(items);
},[items]);
Enter fullscreen mode Exit fullscreen mode

useMemo vs useCallback

These Hooks are related but different.

Hook Purpose
useMemo Memoize values
useCallback Memoize functions

Example:

useMemo:

const total = useMemo(()=>calculateTotal(),[]);
Enter fullscreen mode Exit fullscreen mode

useCallback:

const handleClick = useCallback(()=>{
 saveData();
},[]);
Enter fullscreen mode Exit fullscreen mode

We will explore useCallback in detail in the next article.


Real-World Example: Banking Dashboard

Imagine a dashboard displaying:

  • Account summary
  • Transaction history
  • Spending analysis

Data:

Transactions
      |
      ↓
Calculate Monthly Spending
      |
      ↓
Generate Chart Data
      |
      ↓
Render Dashboard
Enter fullscreen mode Exit fullscreen mode

Without optimization:

Every UI update
        |
        ↓
Recalculate Everything
Enter fullscreen mode Exit fullscreen mode

With useMemo:

Transactions Changed
        |
        ↓
Recalculate Chart Data

Otherwise

Reuse Previous Calculation
Enter fullscreen mode Exit fullscreen mode

Common Mistakes

1. Using useMemo Everywhere

Incorrect:

const name = useMemo(()=>user.name,[user]);
Enter fullscreen mode Exit fullscreen mode

This creates unnecessary complexity.

Use useMemo only for:

  • Expensive calculations
  • Performance issues

2. Incorrect Dependencies

Example:

useMemo(()=>{
 return calculate(items);
},[]);
Enter fullscreen mode Exit fullscreen mode

Problem:

items is missing.

Correct:

useMemo(()=>{
  return calculate(items);
},[items]);
Enter fullscreen mode Exit fullscreen mode

3. Mutating useRef Values Expecting UI Updates

Example:

count.current++;
Enter fullscreen mode Exit fullscreen mode

The UI will not update.

Use:

setCount()
Enter fullscreen mode Exit fullscreen mode

when UI should change.


Best Practices

  • Use useRef for persistent mutable values.
  • Use useRef when you need DOM access.
  • Use useMemo only for expensive computations.
  • Do not optimize prematurely.
  • Keep dependency arrays accurate.
  • Measure performance before optimization.

Key Takeaways

Today, we learned:

useRef stores values without causing re-renders.
useRef can access DOM elements directly.
useMemo caches expensive calculations.
useMemo improves performance when used correctly.
useState, useEffect, useRef, and useMemo solve different problems.


Coming Next 🚀

In Day 15, we will explore:

React Hooks Deep Dive – useCallback and React.memo

We will learn:

  • Function memoization
  • Preventing unnecessary child renders
  • React.memo optimization
  • Passing callbacks efficiently
  • Performance optimization patterns
  • Real-world component optimization

These concepts are extremely important when building large-scale React applications.

Happy Coding! 🚀

Top comments (0)