Unlocking the Power of useRef in React: 12 Essential Use Cases
Tags: javascript, useref, react, hooks
Published: 2023-08-24 16:07:19

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.

```javascript
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>
  );
}

```
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.

```javascript
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>
  );
}

```
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.

```javascript
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>;
}

```
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.

```javascript
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} />;
}

```
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.

```javascript
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 };
}

```

### 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.

```javascript
import { useRef, useEffect } from 'react';

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

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

  return ref.current;
}

```
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`**

```javascript
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;
}

```
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`**

```javascript
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;
}

```
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:

```javascript
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;
}

```
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.

```javascript
import { useRef, useEffect } from 'react';

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

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

  return isMounted;
}

```
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.

```javascript
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;
}

```
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.

```javascript
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 };
}

```
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.

```javascript
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]);
}

```
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.

```javascript
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>
  );
}

```
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.

```javascript
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>
  );
}

```
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.

```javascript
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>
  );
}

```
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:

```javascript
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;

```
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.

```javascript
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>
  );
}

```
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.