Hey there, fellow React enthusiasts! It’s your friendly neighborhood coding bear, “Coding Bear,” back with another deep dive into the wonderful world of React. With over two decades of wrestling with components and state, I’ve seen patterns come and go. Today, we’re tackling a classic yet often misunderstood tool in the React hooks arsenal: useRef. Specifically, we’re going to explore the powerful pattern of using useRef to remember previous values of props or state. This isn’t just a neat trick; it’s a fundamental technique for writing robust, efficient, and clean React components. Whether you’re debugging a tricky state transition, optimizing performance, or implementing specific lifecycle behaviors, understanding how to track previous values is a must-have skill. Let’s get our paws dirty and unlock the full potential of useRef!
Before we jump into the code, let’s understand the “why.” In React’s functional component paradigm, everything is re-created on every render. The props and state variables you get inside your component function are snapshots of that specific render. This is a core tenet of React’s mental model and is crucial for predictability. However, there are legitimate scenarios where you need to know not just the current value, but also what the value was in the previous render. Imagine these situations:
useEffect only when a specific property changes from one known value to another, not just when it changes at all.true, but only if it was previously false.useState hook, you’ll immediately create an infinite loop. Setting state causes a re-render, which would then set the state again, and so on. We need a tool that persists data across renders without triggering a re-render. Enter useRef.
useRef returns a mutable object whose .current property is initialized to the passed argument. This object persists for the full lifetime of the component. Crucially, mutating the .current property does not cause a re-render. This makes it the perfect vessel for storing information that is relevant to the component’s instance but isn’t part of its visual output data flow.// This is the basic signature of useRefconst myRef = useRef(initialValue);// Access and mutate the value via myRef.current
📊 If you’re into learning and personal growth, Mastering Reacts useEffect Hook From Dependency Array Pitfalls to Professional Patternsfor more information.
The most common and robust pattern for tracking a previous value involves a beautiful dance between useRef and useEffect. The useEffect hook allows us to perform side effects after the render is committed to the screen. This is the ideal time to capture what the “current” value is before it potentially changes on the next render.
Let’s look at a concrete example. Suppose we have a component that receives a user prop, and we want to log a message whenever the user.id changes.
import React, { useEffect, useRef } from 'react';function UserProfile({ user }) {// Step 1: Create a ref to hold the previous user.idconst prevUserIdRef = useRef();useEffect(() => {// Step 2: Inside useEffect, we have access to the current render's values.// The ref still holds the value from the *previous* render (or initial undefined).if (prevUserIdRef.current !== undefined && prevUserIdRef.current !== user.id) {console.log(`User ID changed from ${prevUserIdRef.current} to ${user.id}`);// You could trigger an animation, analytics event, etc., here.}// Step 3: AFTER logging/comparing, update the ref for the *next* render.prevUserIdRef.current = user.id;}, [user.id]); // Step 4: The effect depends on `user.id`. It runs after every render where `user.id` is different.return <div>User ID: {user.id}</div>;}
Let’s break down the render cycle:
prevUserIdRef.current is undefined. The useEffect runs. The if condition is false (undefined !== undefined). We then set prevUserIdRef.current = user.id. Let’s say user.id is 123.user.id is still 123. prevUserIdRef.current is 123. The useEffect runs because user.id is in the dependency array. The if condition is false (123 !== 123). We set prevUserIdRef.current = user.id (still 123).user.id is now 456. prevUserIdRef.current is still 123. The useEffect runs. The if condition is true (123 !== undefined && 123 !== 456). We log the change. We then update prevUserIdRef.current = 456.
This pattern is elegant and safe. The update of the ref (prevUserIdRef.current = user.id) happens inside the useEffect, which runs after the render. This ensures we are always comparing the newly rendered value to the one from the immediately preceding render.
🔎 Looking for a hidden gem or trending restaurant? Check out NOVY Restaurant to see what makes this place worth a visit.
usePrevious HookGood React developers are lazy in the best way possible—we abstract repetitive patterns into reusable custom hooks. The previous value pattern is a prime candidate. Let’s create a generic usePrevious hook.
// usePrevious.jsimport { useRef, useEffect } from 'react';function usePrevious(value) {const ref = useRef();useEffect(() => {ref.current = value;}, [value]); // Only re-run if value changesreturn ref.current; // This returns the *previous* value during this render.}
Now, our UserProfile component becomes beautifully simple:
import React from 'react';import usePrevious from './usePrevious';function UserProfile({ user }) {const prevUserId = usePrevious(user.id);if (prevUserId !== undefined && prevUserId !== user.id) {console.log(`User ID changed from ${prevUserId} to ${user.id}`);// Note: This log runs during the render phase.// For side-effects, you would still need useEffect.}return <div>User ID: {user.id}</div>;}
Important Nuance: Notice the difference. Our custom usePrevious hook returns ref.current during the render. At the moment of this render, ref.current still points to the value from the previous render cycle (because the useEffect that updates it hasn’t run yet for this cycle). This makes the previous value available directly in the render logic, which is fantastic for conditional rendering.
However, be cautious! If you need to perform a side effect (like logging to console, making an API call, manipulating the DOM directly) based on the change, you should still do it inside a useEffect. Performing side effects directly in the render function can lead to inconsistencies with React’s concurrent features and is generally considered an anti-pattern.
Let’s see a more complex example comparing multiple previous props:
function ComplexComponent({ count, status }) {const prevCount = usePrevious(count);const prevStatus = usePrevious(status);useEffect(() => {// Side effect based on count changeif (prevCount !== undefined && prevCount !== count) {console.log(`Count jumped from ${prevCount} to ${count}`);}}, [count, prevCount]);useEffect(() => {// Side effect based on status changeif (prevStatus !== undefined && prevStatus !== status) {document.title = `Status: ${status}`;}}, [status, prevStatus]);// Render logic using previous valuesconst isNewlyActive = status === 'active' && prevStatus !== 'active';return (<div><p>Count: {count}</p><p>Status: {status}</p>{isNewlyActive && <p>Welcome to active status!</p>}</div>);}
This demonstrates the full power: separating side-effect logic in useEffect and derived render state in the main function body, both leveraging the clean abstraction of usePrevious.
Whether you’re budgeting, studying, or just need fast math help, this basic calculator tool with history gets the job done.
And there you have it! The humble useRef hook, often relegated to DOM node references, reveals itself as an indispensable tool for managing temporal state across renders. By combining it with useEffect, we can reliably track previous props and state, opening doors to sophisticated component behavior, better debugging, and performance optimizations. Remember the key principle: useRef gives you a mutable box that lives outside the render cycle. Use it to remember anything you need without triggering re-renders.
The pattern of creating a usePrevious custom hook is a mark of a seasoned React developer. It encapsulates complexity, promotes reusability, and makes your component code declarative and clean. As you continue your React journey, keep this tool in your belt. You’ll be surprised how often you reach for it.
This is “Coding Bear,” signing off. Keep your components pure, your state predictable, and your refs useful. Happy coding, and I’ll see you in the next post! Feel free to roar in the comments with your own use cases or questions.
To minimize the risk of hacking, it’s smart to rely on a secure password generator tool that creates complex passwords automatically.
