ReactJS

 

Mastering useRef in ReactJS: Unleashing the Power of DOM Element Manipulation and Mutable Variables

ReactJS is a popular JavaScript library for building user interfaces, and it provides a wide range of tools and features to simplify the development process. One such feature is the useRef hook, which allows developers to work with DOM elements and mutable variables in a more efficient manner. In this blog post, we will dive deep into the useRef hook and explore its capabilities, along with providing code samples to illustrate its usage.

Mastering useRef in ReactJS: Unleashing the Power of DOM Element Manipulation and Mutable Variables

1. Understanding the useRef Hook

The useRef hook is part of the React Hooks API introduced in React version 16.8. It provides a way to create a mutable value that persists across re-renders without causing the component to re-render itself. useRef returns a mutable ref object that can be attached to any DOM element or used to hold any mutable value.

2. Working with DOM Elements using useRef

One of the primary use cases of useRef is to reference DOM elements within functional components. Traditional methods, such as using getElementById or querySelector, are not ideal when working with React components because they break the declarative nature of React. useRef allows us to access DOM elements in a more React-friendly way.

Let’s consider an example where we want to focus on an input field automatically when a component renders. We can achieve this using the useRef hook as shown in the code snippet below:

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

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

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

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

In this example, we create a ref object using the useRef hook and assign it to the inputRef variable. We then attach the ref object to the input element using the ref attribute. Inside the useEffect hook, we use inputRef.current to access the actual DOM node of the input element and call the focus method to bring focus to it. By using useRef, we avoid breaking the React component model and achieve the desired functionality.

3. Managing Mutable Variables with useRef

In addition to working with DOM elements, useRef can also be used to hold mutable values that persist across re-renders. Unlike state variables created with useState, changes to the value held by useRef do not cause a re-render of the component.

Consider the following example where we want to keep track of the number of times a co

In this example, we create a ref object using the useRef hook and assign it to the inputRef variable. We then attach the ref object to the input element using the ref attribute. Inside the useEffect hook, we use inputRef.current to access the actual DOM node of the input element and call the focus method to bring focus to it. By using useRef, we avoid breaking the React component model and achieve the desired functionality.

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

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

  useEffect(() => {
    renderCount.current += 1;
    console.log(`Component rendered ${renderCount.current} times.`);
  });

  return null;
}

In this case, we create a ref object called renderCount and initialize it with a value of 0. Inside the useEffect hook, we increment the renderCount.current value by 1 every time the component renders. The value of renderCount.current persists across re-renders, and we can log the updated count to the console.

Note that we don’t provide an empty dependency array ([ ]) to the useEffect hook, which means it will run on every render. This ensures that the render count is always updated correctly.

In addition to referencing DOM elements and managing mutable variables, useRef can also be used for other advanced scenarios. Let’s explore a couple of those use cases:

3.1 Preserving Values between Renders

In certain cases, you may encounter situations where you need to preserve a value between renders, but you don’t want it to trigger a re-render when it changes. useRef can help in such scenarios. Let’s consider an example where we want to store the previous value of a prop and compare it with the current value:

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

function ValueComparator({ value }) {
  const previousValue = useRef(value);

  useEffect(() => {
    console.log(`Previous value: ${previousValue.current}`);
    console.log(`Current value: ${value}`);

    previousValue.current = value;
  }, [value]);

  return null;
}

Here, we create a ref object called previousValue and initialize it with the initial value of the prop. Inside the useEffect hook, we compare the previous value (previousValue.current) with the current value of the prop. Whenever the prop changes, the effect will run, and we update the previousValue.current with the new value. This allows us to keep track of the previous value without triggering a re-render.

3.2. Referencing Child Components

Sometimes, you may need to interact with child components directly from a parent component. useRef can help in this situation by providing a way to reference child components and access their methods or properties. Let’s consider an example where we want to reset the state of a child component from the parent:

import React, { useRef } from 'react';

function ChildComponent({ onReset }) {
  const reset = () => {
    // Reset logic here
  };

  useImperativeHandle(onReset, () => ({
    reset,
  }));

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

function ParentComponent() {
  const childRef = useRef(null);

  const handleReset = () => {
    childRef.current.reset();
  };

  return (
    <div>
      <ChildComponent onReset={childRef} />
      <button onClick={handleReset}>Reset Child</button>
    </div>
  );
}

In this example, we create a child component that exposes a reset method using the useImperativeHandle hook. We pass a ref object (childRef) from the parent component to the child component using the onReset prop. The parent component can then access the child component’s reset method using childRef.current.reset() and trigger it when needed, such as on a button click.

By using useRef in conjunction with useImperativeHandle, we establish a communication channel between parent and child components, enabling us to interact with child components in a controlled manner.


In addition to referencing DOM elements and managing mutable variables, useRef can also be used for other advanced scenarios. Let’s explore a couple more use cases:

3.3. Synchronizing State and Uncontrolled Components

React encourages the use of controlled components, where the component’s state is managed by React itself. However, there are situations where uncontrolled components may be necessary, such as when working with third-party libraries or integrating with legacy code. useRef can be used to synchronize the state between the uncontrolled component and React’s state.

import React, { useRef } from 'react';

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

  const handleClick = () => {
    console.log(inputRef.current.value);
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleClick}>Submit</button>
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>Uncontrolled Component Example</h1>
      <UncontrolledComponent />
    </div>
  );
}

In this example, the UncontrolledComponent renders an input element and a button. By using useRef, we create a ref object called inputRef and attach it to the input element. When the button is clicked, we can access the input value using inputRef.current.value. Although the input element is uncontrolled, we can still access its value when needed.

3.4. Caching Expensive Computations

Sometimes, you may have a computation that is expensive in terms of performance or resource usage. In such cases, you can use useRef to cache the computed value, avoiding unnecessary re-computations.

import React, { useRef } from 'react';

function ExpensiveComputation() {
  const resultRef = useRef(null);

  const performComputation = () => {
    if (!resultRef.current) {
      // Expensive computation
      resultRef.current = // Computed result
    }

    return resultRef.current;
  };

  return (
    <div>
      <p>Result: {performComputation()}</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>Expensive Computation Example</h1>
      <ExpensiveComputation />
    </div>
  );
}

In this example, the ExpensiveComputation component performs a computation and caches the result using useRef. The first time the component renders, the expensive computation is performed and the result is stored in the ref object. On subsequent renders, the cached result is returned directly, avoiding the expensive computation.

3.5 Imperative Animations and Third-Party Libraries

In certain cases, you may need to work with imperative animations or interact with third-party libraries that require direct access to DOM elements. useRef can be a handy tool to facilitate these scenarios.

Let’s consider an example where we want to animate an element using the GSAP library:

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

function AnimationComponent() {
  const elementRef = useRef(null);

  useEffect(() => {
    gsap.fromTo(elementRef.current, { opacity: 0, y: -100 }, { opacity: 1, y: 0, duration: 1 });
  }, []);

  return <div ref={elementRef}>Animate me!</div>;
}

function App() {
  return (
    <div>
      <h1>Imperative Animation Example</h1>
      <AnimationComponent />
    </div>
  );
}

In this example, we import the GSAP library and create a ref object called elementRef. We attach this ref to the div element using the ref attribute. Inside the useEffect hook, we use the gsap function to animate the element. By passing elementRef.current as the target, we can directly access the DOM element and apply the animation. The animation will run when the component mounts, thanks to the empty dependency array ([ ]).

Using useRef in this way allows us to seamlessly integrate with imperative animations or third-party libraries that require direct access to DOM elements.


In addition to the discussed use cases, it’s important to note that the useRef hook has other applications as well. Some of these include:

3.6. Managing Timers and Intervals

When working with timers or intervals in React, using useRef can help in preserving the reference to the timer ID or interval ID across re-renders. This allows you to clear or manipulate the timer or interval as needed. Here’s an example:

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

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

  useEffect(() => {
    timerRef.current = setInterval(() => {
      console.log('Timer tick');
    }, 1000);

    return () => {
      clearInterval(timerRef.current);
    };
  }, []);

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

function App() {
  return (
    <div>
      <h1>Timer Example</h1>
      <TimerComponent />
    </div>
  );
}

In this example, we create a ref object called timerRef and assign it to the current value of the setInterval function. We use the useEffect hook to start the timer when the component mounts. The returned cleanup function clears the interval using clearInterval(timerRef.current) when the component unmounts.

3.7. Storing Previous Values for Comparison

In certain scenarios, you may need to compare the previous and current values of a variable or state. useRef can be used to store the previous value and allow you to perform comparisons easily. Here’s an example:

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

function ValueComparator() {
  const [value, setValue] = useState('');
  const previousValueRef = useRef('');

  useEffect(() => {
    if (value !== previousValueRef.current) {
      console.log('Value changed:', value);
      previousValueRef.current = value;
    }
  }, [value]);

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>Value Comparison Example</h1>
      <ValueComparator />
    </div>
  );
}

In this example, we have an input field that updates the state value. We use the useRef hook to store the previous value, which is initialized as an empty string. Inside the useEffect hook, we compare the current value with the previous value using value !== previousValueRef.current. If there’s a change, we log it to the console and update the previous value with the current value.

Conclusion

In this blog post, we delved deeper into the useRef hook in ReactJS and explored additional use cases that showcase its versatility and practicality. From managing timers and intervals to storing previous values for comparison, useRef proves to be an indispensable tool in a React developer’s toolkit.

By understanding and leveraging the capabilities of useRef, you can write more efficient and optimized React components. Whether it’s preserving references, managing previous values, or synchronizing state with external libraries, useRef empowers you to achieve better control and performance in your React applications.

Remember to explore other React hooks such as useState, useEffect, and useContext to further enhance your React development skills. Continuously experiment and embrace the power of React’s functional components to build remarkable user interfaces and scalable applications.

Keep coding, keep learning, and leverage the useRef hook to unlock the full potential of ReactJS in your development journey. Happy coding!

Previously at
Flag Argentina
Argentina
time icon
GMT-3
Seasoned Software Engineer specializing in React.js development. Over 5 years of experience crafting dynamic web solutions and collaborating with cross-functional teams.