DEV Community

Sam Abaasi
Sam Abaasi

Posted on

Unlocking the Power of useRef in React: 12 Essential Use Cases

React's useRef hook is often seen as a tool for interacting with the DOM, but its versatility extends far beyond that. It's a powerful utility that can enhance various aspects of your React applications. In this article, we'll explore practical use cases of useRef in important concepts of React, from accessibility to animations and more.
Let's explore some practical use cases:

  • 1. Focus Management and Accessibility
  • 2. Managing Animations and Transitions
  • 3. Clearing Timers and Side Effects
  • 4. Integrating with Third-Party Libraries
  • 5. Measuring Element Dimensions
  • 6. Creating a usePrevious Hook
  • 7. useCallback and useMemo Optimizations
  • 8. Debouncing and Throttling
  • 9. Keeping Track of Component Mounted State
  • 10. Caching API Responses
  • 11. Scroll Position Tracking
  • 12. Handling Keyboard Shortcuts

And here are 5 additional use cases:

  • 13. Conditional Rendering: Toggle components' visibility without affecting React's state.
  • 14. Implementing Infinite Scrolling: Track scroll events for infinite scrolling.
  • 15. Custom Hooks: Encapsulate logic and maintain references specific to that logic.
  • 16. Toggle Visibility: Quickly toggle the visibility of elements or components.
  • 17. Enhance Form Handling: Directly access and manipulate form input values.

1. Focus Management and Accessibility

Managing focus is crucial for creating accessible web applications. With useRef, you can easily control the focus of DOM elements, improving the user experience and accessibility.

import { useRef } from 'react';

function FocusableInput() {
  const inputRef = useRef(null);

  const focusInput = () => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

In this example, inputRef references the input element. Clicking the button triggers the focusInput function, which utilizes inputRef to focus the input element.

2. Managing Animations and Transitions

useRef can be used to manage animations or transitions by keeping track of animation-related state without causing unnecessary re-renders.

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

function AnimatedElement() {
  const elementRef = useRef(null);
  const [isAnimating, setIsAnimating] = useState(false);

  useEffect(() => {
    if (elementRef.current && isAnimating) {
      // Perform animation logic here
      // Add CSS classes or apply animations
    }
  }, [isAnimating]);

  return (
    <div ref={elementRef} className={isAnimating ? 'animated' : ''}>
      {/* Content */}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

In this example, elementRef references the animated element, and the isAnimating state triggers animations when it changes.

3. Clearing Timers and Side Effects

useRef can help with clearing timers and other side effects when a component unmounts.

import { useEffect, useRef } from 'react';

function TimerComponent() {
  const timerRef = useRef(null);

  useEffect(() => {
    timerRef.current = setInterval(() => {
      // Timer logic
    }, 1000);

    return () => {
      // Clear the timer when the component unmounts
      clearInterval(timerRef.current);
    };
  }, []);

  return <div>Timer Component</div>;
}

Enter fullscreen mode Exit fullscreen mode

Here, timerRef stores the timer reference, ensuring it's cleared when the component unmounts.

4. Integrating with Third-Party Libraries

When working with third-party libraries that require direct access to DOM elements or data outside the React component tree, useRef is your go-to tool.

import { useEffect, useRef } from 'react';
import Chart from 'third-party-chart-library';

function ChartComponent({ data }) {
  const chartContainerRef = useRef(null);
  const chartInstanceRef = useRef(null);

  useEffect(() => {
    if (chartContainerRef.current && !chartInstanceRef.current) {
      chartInstanceRef.current = new Chart(chartContainerRef.current, data);
    }

    return () => {
      if (chartInstanceRef.current) {
        chartInstanceRef.current.destroy();
      }
    };
  }, [data]);

  return <div ref={chartContainerRef} />;
}

Enter fullscreen mode Exit fullscreen mode

In this example, chartContainerRef references the DOM element for rendering the chart, and chartInstanceRef stores the third-party library instance. Cleanup is performed when the component unmounts.

5. Measuring Element Dimensions

You can create a custom hook that measures the dimensions of a DOM element and updates those dimensions when the window is resized.

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

function useElementDimensions() {
  const ref = useRef(null);
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });

  useEffect(() => {
    const updateDimensions = () => {
      if (ref.current) {
        const { width, height } = ref.current.getBoundingClientRect();
        setDimensions({ width, height });
      }
    };

    window.addEventListener('resize', updateDimensions);
    updateDimensions();

    return () => {
      window.removeEventListener('resize', updateDimensions);
    };
  }, []);

  return { ref, dimensions };
}

Enter fullscreen mode Exit fullscreen mode

6. Creating a usePrevious Hook

The usePrevious hook allows you to track the previous value of a variable or state in your component. This can be useful for various scenarios, such as detecting changes or performing animations when a value changes.

import { useRef, useEffect } from 'react';

function usePrevious(value) {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

Enter fullscreen mode Exit fullscreen mode

In this custom hook, useRef is used to create a mutable reference (ref) that persists across renders. The useEffect hook updates the ref.current value whenever the value prop changes, effectively storing the previous value.

7. useCallback and useMemo Optimizations

You can use useRef to optimize the behavior of the useCallback and useMemo hooks. By storing their results in a ref, you can ensure that the functions or memoized values remain consistent across renders without causing unnecessary re-computation.
Optimizing useCallback

import { useCallback, useRef } from 'react';

function useOptimizedCallback(callback, dependencies) {
  const callbackRef = useRef();

  // Store the callback function in the ref
  callbackRef.current = useCallback(callback, dependencies);

  return callbackRef.current;
}

Enter fullscreen mode Exit fullscreen mode

This custom hook, useOptimizedCallback, utilizes useRef to store the memoized callback in the callbackRef. This ensures that the callback remains the same between renders when the dependencies change.

Optimizing useMemo

import { useMemo, useRef } from 'react';

function useOptimizedMemo(callback, dependencies) {
  const memoRef = useRef();

  // Store the memoized value in the ref
  memoRef.current = useMemo(callback, dependencies);

  return memoRef.current;
}

Enter fullscreen mode Exit fullscreen mode

Similarly, useOptimizedMemo uses useRef to cache the memoized value. This prevents the expensive calculation within useMemo from being repeated on every render when dependencies change.

8. Debouncing and Throttling

useRef can be used to implement debouncing and throttling in custom hooks. These techniques are helpful for limiting the frequency of certain operations, such as handling user input or API requests.

Here's an example of a simple debounce hook:

import { useRef } from 'react';

function useDebounce(callback, delay) {
  const timeoutRef = useRef();

  function debounce(...args) {
    clearTimeout(timeoutRef.current);
    timeoutRef.current = setTimeout(() => {
      callback(...args);
    }, delay);
  }

  return debounce;
}

Enter fullscreen mode Exit fullscreen mode

In this custom useDebounce hook, a timeoutRef is created using useRef to store the timeout reference across renders. The debounce function cancels the previous timeout and sets a new one when called.

9. Keeping Track of Component Mounted State

useRef can be used to maintain the mounted state of a component. This can help prevent memory leaks when async operations complete after a component has unmounted.

import { useRef, useEffect } from 'react';

function useIsMounted() {
  const isMounted = useRef(true);

  useEffect(() => {
    return () => {
      isMounted.current = false;
    };
  }, []);

  return isMounted;
}

Enter fullscreen mode Exit fullscreen mode

The useIsMounted hook returns a ref object (isMounted) that is initially true. An effect is used to update the isMounted value to false when the component unmounts. This can be used to safely handle async operations in your component.

10. Caching API Responses

You can use useRef to cache API responses or other expensive data to avoid redundant network requests or calculations.

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

function useCachedData(apiCall) {
  const dataRef = useRef(null);
  const [data, setData] = useState(null);

  useEffect(() => {
    if (dataRef.current !== null) {
      // Use cached data
      setData(dataRef.current);
      return;
    }

    // Make the API call
    apiCall().then((result) => {
      // Cache the result
      dataRef.current = result;
      setData(result);
    });
  }, [apiCall]);

  return data;
}

Enter fullscreen mode Exit fullscreen mode

In this custom hook, dataRef is used to cache the API response. If the data is already cached, it's used directly, avoiding additional API requests.

11. Scroll Position Tracking

You can create a custom hook for tracking the scroll position of a container element.

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

function useScrollPosition(containerRef) {
  const [scrollPosition, setScrollPosition] = useState({ x: 0, y: 0 });
  const previousPositionRef = useRef({ x: 0, y: 0 });

  useEffect(() => {
    function handleScroll() {
      if (containerRef.current) {
        const { scrollLeft, scrollTop } = containerRef.current;
        setScrollPosition({ x: scrollLeft, y: scrollTop });
      }
    }

    const container = containerRef.current;
    if (container) {
      container.addEventListener('scroll', handleScroll);
    }

    return () => {
      if (container) {
        container.removeEventListener('scroll', handleScroll);
      }
    };
  }, [containerRef]);

  useEffect(() => {
    previousPositionRef.current = scrollPosition;
  }, [scrollPosition]);

  return { scrollPosition, previousScrollPosition: previousPositionRef.current };
}

Enter fullscreen mode Exit fullscreen mode

In this hook, useRef is used to store the previous scroll position, allowing you to track both the current and previous scroll positions within your component.

12. Handling Keyboard Shortcuts

You can create a custom hook for handling keyboard shortcuts in your application.

import { useEffect, useRef } from 'react';

function useKeyboardShortcut(key, callback) {
  const callbackRef = useRef(callback);

  useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);

  useEffect(() => {
    function handleKeyPress(event) {
      if (event.key === key) {
        callbackRef.current();
      }
    }

    window.addEventListener('keydown', handleKeyPress);

    return () => {
      window.removeEventListener('keydown', handleKeyPress);
    };
  }, [key]);
}

Enter fullscreen mode Exit fullscreen mode

In this hook, a callbackRef is used to keep a reference to the callback function. When the key or the callback changes, the callbackRef is updated. When the specified key is pressed, the stored callback is invoked.

Here are some other use cases:

1. Handling Form Input

You can use useRef to access and manipulate form input values directly without relying on React state management. This is particularly useful for certain advanced form handling scenarios.

import React, { useRef } from 'react';

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

  const handleButtonClick = () => {
    // Access and manipulate the input value directly
    alert(`Input value: ${inputRef.current.value}`);
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleButtonClick}>Get Input Value</button>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

In this example, the inputRef is used to directly access the input element's value when the button is clicked, without storing it in React state.

2. Conditional Rendering

You can conditionally render components or elements based on specific conditions in your application. useRef can help with this by allowing you to toggle rendering without affecting the component's state.

import React, { useRef } from 'react';

function ConditionalRenderingExample() {
  const shouldRender = useRef(true);

  const toggleRender = () => {
    shouldRender.current = !shouldRender.current;
    // This won't trigger a re-render
  };

  return (
    <div>
      <button onClick={toggleRender}>Toggle Rendering</button>
      {shouldRender.current && <p>Render this conditionally</p>}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

In this example, the shouldRender ref is used to conditionally render the <p> element based on the toggle button click without managing the condition in state.

3. Implementing Infinite Scrolling

Infinite scrolling is a common UI pattern where additional content is loaded as the user scrolls down a page. useRef can help implement this by tracking scroll events.

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

function InfiniteScrollExample() {
  const containerRef = useRef();
  const [items, setItems] = useState([]);
  const loading = useRef(false);

  const loadMoreData = () => {
    if (loading.current) return;
    loading.current = true;

    // Simulate loading additional data
    setTimeout(() => {
      const newItems = [...items, ...Array(10).fill('New Item')];
      setItems(newItems);
      loading.current = false;
    }, 1000);
  };

  useEffect(() => {
    const container = containerRef.current;

    const handleScroll = () => {
      if (container.scrollTop + container.clientHeight >= container.scrollHeight - 20) {
        loadMoreData();
      }
    };

    container.addEventListener('scroll', handleScroll);

    return () => {
      container.removeEventListener('scroll', handleScroll);
    };
  }, [items]);

  return (
    <div ref={containerRef} style={{ height: '400px', overflow: 'auto' }}>
      {items.map((item, index) => (
        <div key={index}>{item}</div>
      ))}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

In this example, useRef is used to manage the loading state and to track scroll events for implementing infinite scrolling behavior.

4. Custom Hooks

Custom hooks allow you to encapsulate and share logic across components. You can use useRef within custom hooks to maintain references or state specific to that logic.

Here's a simplified example of a custom hook for handling the visibility of a modal:

import React, { useState } from 'react';

// Custom hook
function useModal() {
  const modalRef = useRef(null);

  const openModal = () => {
    if (modalRef.current) {
      modalRef.current.style.display = 'block';
    }
  };

  const closeModal = () => {
    if (modalRef.current) {
      modalRef.current.style.display = 'none';
    }
  };

  return { modalRef, openModal, closeModal };
}

function App() {
  const { modalRef, openModal, closeModal } = useModal();

  return (
    <div>
      <button onClick={openModal}>Open Modal</button>
      <button onClick={closeModal}>Close Modal</button>
      <div className="modal" ref={modalRef}>
        {/* Modal content */}
      </div>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

In this example, useModal is a custom hook that manages the visibility of a modal element using useRef. It encapsulates the modal's logic, making it reusable across components.

5. Toggle Visibility

You can use useRef to toggle the visibility of elements or components without involving React's state management. This can be handy for quick and simple UI interactions.

import React, { useRef } from 'react';

function ToggleVisibilityExample() {
  const elementRef = useRef();

  const toggleVisibility = () => {
    if (elementRef.current) {
      elementRef.current.style.display === 'none'
        ? (elementRef.current.style.display = 'block')
        : (elementRef.current.style.display = 'none');
    }
  };

  return (
    <div>
      <button onClick={toggleVisibility}>Toggle Visibility</button>
      <div ref={elementRef} style={{ display: 'none' }}>
        Element to Toggle
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

In this example, the elementRef is used to toggle the visibility of the <div> element when the button is clicked.

Conclusion

React's useRef hook is a Swiss army knife for optimizing and extending the capabilities of your React components. Whether you need to manage focus, control animations, or optimize the behavior of other hooks like useCallback and useMemo, useRef is a valuable tool in your React toolkit. It empowers you to efficiently handle various scenarios without adding unnecessary complexity to your components.

Incorporating useRef into your React applications opens up new possibilities for fine-tuning performance and implementing advanced features. So, don't underestimate the power of this seemingly simple hook—it can make a significant difference in the quality and efficiency of your React code.

Top comments (0)