Mastering Refs in React: Practical Guide with useRef and forwardRef
Tags: react, webdev, javascript, programming
Published: 2024-03-24 02:10:32

Welcome to an in-depth tutorial where we explore the practical use of React’s useRef and forwardRef. By the end of this post, you'll understand how to effectively use these hooks to control and manipulate DOM elements and component instances in a React application.

**Introduction**

In React, managing focus, reading or manipulating the DOM directly is generally not recommended as it goes against the core principles of React’s declarative nature. However, there are scenarios where direct DOM manipulation becomes necessary, such as managing focus, measuring the size of an element, or integrating with third-party DOM libraries. In such cases, React provides us with `useRef` and `forwardRef`.

Let’s dive into a practical example to understand how to use `useRef` and `forwardRef` in a Next.js project that displays emojis and their meanings. We'll be building a feature where clicking an emoji scrolls the corresponding meaning into view. I will attach my Git hub repo [here](https://github.com/hmalindu1/use-ref-hook). You can run this locally and see the result.

**Project Structure**

Our Next.js project is structured as follows:


![app file structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/93jn1zjl967856pmbn2w.png)


- app: Contains our main page page.tsx

- components: Houses our React components including EmojiRow, EmojiTile, MeaningCard, and MeaningList.

- constants: Stores our data files emojiData.tsx and meaningData.tsx

- types: Contains type definitions used in our project.


![UI plan](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h8i68yggxyq0sxia4few.png)


First Let’s look at our constants.

**emojiData.tsx**

```js
import { EmojiDataType } from "@/types/types";

export const EMOJI_DATA: EmojiDataType[] = [
  {
    icon: "🖖",
    name: "Prosper",
  },
  {
    icon: "🤞",
    name: "Luck",
  },
  {
    icon: "👌",
    name: "Perfect",
  },
  {
    icon: "🤟",
    name: "Love",
  },
  {
    icon: "🙏",
    name: "Pray",
  },
];
```
**meaningData.tsx**
```js
import { MeaningDataType } from "@/types/types";

export const MEANING_DATA: MeaningDataType[] = [
  {
    name: "Prosper",
    description: "Live Long and Prosper",
  },
  {
    name: "Luck",
    description: "Fingers crossed, I wish you Good Luck!",
  },
  {
    name: "Perfect",
    description: "This is Perfect!",
  },
  {
    name: "Love",
    description: "I Love You 💕",
  },
  {
    name: "Pray",
    description: "God will always be with you",
  },
];
```
My plan is to when I click on Emoji Tile named `Propser` I am going to reference the Meaning Card inside the Meaning List.


![Button click should map the meaning card](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ietkzhi0h2zm3u526okw.png)

**Using ‘useRef’ in ‘page.tsx’**

In our `page.tsx`, we utilize `useRef` to create references for different emoji meanings:

```js
import { useRef } from "react";
const Home = () => {
  const meaningRefs = {
    Prosper: useRef(null),
    Luck: useRef(null),
    Perfect: useRef(null),
    Love: useRef(null),
    Pray: useRef(null),
  };
};
```
**Full code**

```js
"use client";

import EmojiRow from "@/components/EmojiRow";
import MeaningList from "@/components/MeaningList";
import { useRef } from "react";
const Home = () => {
  const meaningRefs = {
    Prosper: useRef(null),
    Luck: useRef(null),
    Perfect: useRef(null),
    Love: useRef(null),
    Pray: useRef(null),
  };

  return (
    <div className="flex min-h-screen flex-col items-center justify-center gap-10">
      <h1 className="text-3xl font-medium">Mastering Refs in React 🧐</h1>
      <div className="flex gap-7">
        <EmojiRow meaningRefs={meaningRefs} />
      </div>
      <div className="flex flex-col gap-7 w-[500px] max-h-[calc(100vh-910px)] overflow-y-auto shadow-inner">
        <MeaningList meaningRefs={meaningRefs} />
      </div>
    </div>
  );
};

export default Home;
```

Here, `useRef` is used to create an object `meaningRefs` where each property is a ref corresponding to a particular meaning. `useRef` returns a mutable ref object whose .current property is initialized to the passed argument (null in this case). The object persists for the full lifetime of the component.

We create a dictionary of meaning references using the `useRef` hook. Then insert the whole object into props. To choose which key-value pair to use from the `meaningRefs` object passed as a prop, we would typically access the pair by its key. The key is a string that identifies the particular meaning ref we want to use.

**The ‘EmojiTile’ Component**

The `EmojiTile` component represents an individual emoji that users can interact with. When clicked, it triggers a scroll action that brings the corresponding meaning into view. Here's how we implement the `EmojiTile`:

```js
"use client";

import { EmojiDataType } from "@/types/types";

interface EmojiTileProps {
  emoji: EmojiDataType;
  onClick: () => void;
}

const EmojiTile = ({ emoji, onClick }: EmojiTileProps) => {
  return (
    <div
      className="w-20 h-20 shadow flex items-center justify-center rounded-md bg-black/5 hover:bg-black/10 cursor-pointer"
      onClick={onClick}
    >
      {emoji.icon}
    </div>
  );
};

export default EmojiTile;
```

**Implementing Scroll Behavior in ‘EmojiRow’**

In our EmojiRow component, we handle the click event on each emoji. When an emoji is clicked, we want to scroll the corresponding meaning into view:

```js
"use client";

import { EMOJI_DATA } from "@/constants/emojiData";
import EmojiTile from "./EmojiTile";

type MeaningRefs = {
  [key: string]: React.RefObject<HTMLDivElement>;
};

const EmojiRow = ({ meaningRefs }: { meaningRefs: MeaningRefs }) => {
  return EMOJI_DATA.map((emoji, index) => (
    <div key={emoji.name} className="">
      <EmojiTile
        emoji={emoji}
        onClick={() =>
          meaningRefs[emoji.name].current?.scrollIntoView({
            behavior: "smooth",
          })
        }
      />
    </div>
  ));
};

export default EmojiRow;
```

**The ‘MeaningList’ Component**

The `MeaningList` component is responsible for displaying the list of meanings. Each meaning is wrapped in a div that is linked to a specific ref, enabling the scroll-into-view functionality when an emoji is clicked:

```js
"use client";

import { MEANING_DATA } from "@/constants/meaningData";
import MeaningCard from "./MeaningCard";

type MeaningRefs = {
  [key: string]: React.RefObject<HTMLDivElement>;
};


const MeaningList = ({ meaningRefs }: { meaningRefs: MeaningRefs }) => {
  return MEANING_DATA.map((meaning) => (
    <div key={meaning.name} ref={meaningRefs[meaning.name]} >
      <MeaningCard meaning={meaning} />
    </div>
  ));
};

export default MeaningList;
```

This component maps over the `MEANING_DATA` array, creating a list where each item is a `MeaningCard`. By assigning the `ref` from `meaningRefs` to the wrapping `div`, we establish a connection between the emojis and their meanings. When an emoji is clicked, the application scrolls to the corresponding `div` in this list.

**Forwarding Refs with ‘forwardRef’ in ‘MeaningCard’**

In some cases, you might need a child component to expose its DOM node to a parent component. Here’s where `forwardRef` comes into play. In our `MeaningCard` component, we wrap the component with `forwardRef` to forward a ref from its parent:

```js
"use client";

import { MeaningDataType } from "@/types/types";
import { forwardRef, LegacyRef } from "react";

interface MeaningCardProps {
  meaning: MeaningDataType;
}

const MeaningCard = forwardRef(
  ({ meaning }: MeaningCardProps, ref: LegacyRef<HTMLDivElement> | null) => {
    return (
      <div
        className="h-20 bg-black/5 flex items-center justify-center rounded-md shadow"
        ref={ref}
      >
        {meaning.description}
      </div>
    );
  }
);

MeaningCard.displayName = "MeaningCard";

export default MeaningCard;
```

By using `forwardRef`, `MeaningCard` can now receive a `ref` that it attaches to its root element. This allows the parent component to directly reference the DOM node.


![Final Outout](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4aqx4q19x63mc5k6spzc.gif)

I hope now you got an idea about how to use `useRef` to create references to DOM nodes and how to use `forwardRef` to allow child components to expose their DOM nodes to parent components. This pattern is particularly useful in scenarios where you need to directly interact with the DOM, such as controlling focus or scrolling elements into view.

Let’s meet with another practical example in React. Until then, have a good one! Peace ✌️