useRef and forwardRef in React.

useRef and forwardRef in React.

The hook that can remember! ๐Ÿง 

ยท

11 min read

What Is useRef?

useRef is a react hook that is basically used to store the reference of something like let's say a DOM element.

It takes a single argument as initial value and returns an object with a single property called current.

This current holds the reference of whatever we tell useRef to hold to.

Here's a simple view of the syntax and what current property holds can hold!

image.png

image.png

The initial value in case of reference of DOM elements is null because the DOM is still under construction. Once the DOM is built, current property automatically stores the reference of the element that we point it to.

We can make useRef's initial value be anything like Object or string and even update that but that's another story.

The things that make useRef stand out are,

  1. The stored reference does not change on re-render.
  2. Render method is not triggered when the ref.current changes. Yepp!

useRef-meme.jpg

useRef in Action.

Let's see a few good use cases of useRef. Don't worry I wont be showing a simple counter or focus example!

1. Close Modal when clicked outside it.

Here, we'll be building a simple modal, as shown below and when a user clicks outside it, we'll close it.

react-cloud-note-gif.gif

Let's get started! This is what our App component and Modal component will look like.

In App, we make a simple Boolean state to show/ hide the Modal based on user clicks and the Modal component is pretty self explanatory.

App js


import "./styles.css";
import { useState } from "react";

import Modal from "./Modal";
export default function App() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div className="App">
      <h1>Hello World!</h1>
      {showModal ? <Modal setShowModal={setShowModal} /> : null}
      <button onClick={() => setShowModal(true)}>Open Modal</button>
    </div>
  );
}

Modal js


export default function Modal({ setShowModal }) {
  return (
    <div className="modal">
      <div className="modal-content">
        <p>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusantium,
          nesciunt.
        </p>
      </div>
    </div>
  );
}

css


.App {
  font-family: sans-serif;
  text-align: center;
}

.modal {
  position: fixed;
  inset: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: rgb(0, 0, 0, 0.5);
}

.modal-content {
  width: 200px;
  height: 200px;
  background-color: whitesmoke;
  color: #333;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 1rem;
  border-radius: 1rem;
}

image.png

This is what our App looks like as of now and when the button is clicked, showModal is updated to true and the Modal opens and looks as follows.

image.png

Now, when the user clicks outside the content box, we want to close the modal by setting the showModal state to false.

But how do we know if the user clicked outside or not? This is where useRef comes into picture.

Let's import useRef from react and set the ref of the modal-content div into a variable modalRef.

Modal js

import { useRef } from "react";

export default function Modal({ setShowModal }) {
  const modalRef = useRef(null);

  return (
    <div className="modal">
      <div className="modal-content" ref={modalRef}>
        <p>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusantium,
          nesciunt.
        </p>
      </div>
    </div>
  );
}

So, now all we need to do is listen for user clicks and as soon as wee see that the click is within the modal-content div, we'll set the modal state to false

For this, we need a useEffect because we need to listen to events continuously. So, let's write one.

Modal js


import { useRef, useEffect } from "react";

export default function Modal({ setShowModal }) {
  const modalRef = useRef(null);

  useEffect(() => {
    function closeWhenClickedOutside(e) {
      if (modalRef.current && !modalRef.current.contains(e.target)) {
        setShowModal(false);
      }
    }
  }, [modalRef, setShowModal]);

  return (
    <div className="modal">
      <div className="modal-content" ref={modalRef}>
        <p>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusantium,
          nesciunt.
        </p>
      </div>
    </div>
  );
}

So, what's happening here. We make a useEffect and wrote a function closeWhenClickedOutside which takes a single argument event e and modalRef, setShowModal as dependencies.

closeWhenClickedOutside does all the magic for us. When invoked, this function check if the modalRef has current property and then it checks if the event.target i.e the area that we clicked is within the modal content* or outside it.

But what do I mean by contains? It means that if the user clicks on something, and that is not a child of modal content element, it basically means that the user clicked outside the modal content element and the function should close the modal.

Not clear? the below image should give a better view!

image.png

So, modal content element is a div and within that, we have p element.

Click on the white-space inside modal content div, the event target is as shown below.

image.png

And click on the p inside modal content, the event target is as follows.

image.png

Both the clicks show that the event target is within the modal content and when the user clicks outside modal content, we get as follows.

image.png

expanded view

image.png

the modal div element is outside the modal content div , which points that the user has clicked outside and we need to close the modal.

But how do we invoke the closeWhenClickedOutside function?

It's pretty simple, we'll tell the useEffect to add a mousedown event listener on the whole document itself. This will make sure that no matter where the user clicks, we'll know the event.

Modal Js

import { useRef, useEffect } from "react";

export default function Modal({ setShowModal }) {
  const modalRef = useRef(null);

  useEffect(() => {
    function closeWhenClickedOutside(e) {
      console.log(e.target);
      if (modalRef.current && !modalRef.current.contains(e.target)) {
        setShowModal(false);
      }
    }
    document.addEventListener("mousedown", closeWhenClickedOutside);
  }, [modalRef, setShowModal]);

  return (
    <div className="modal">
      <div className="modal-content" ref={modalRef}>
        <p>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusantium,
          nesciunt.
        </p>
      </div>
    </div>
  );
}

Let's see our magic in action!

modal-gif.gif

As you can see, our feature works! Voila ๐Ÿ˜ฒ

YayYesGIF.gif

Now, there is one more thing left to do, clean the eventListener when the component unmounts, this is optional but it's good to do because we don't want events all over the document when we let's say, the user goes to a different page or a different modal mounts.

To do that, all we need to do is use removeEventListener inside the useEffect, in a callback which is runs when the component un-mounts.

Modal Js


import { useRef, useEffect } from "react";

export default function Modal({ setShowModal }) {
  const modalRef = useRef(null);

  useEffect(() => {
    function closeWhenClickedOutside(e) {
      console.log(e.target);
      if (modalRef.current && !modalRef.current.contains(e.target)) {
        setShowModal(false);
      }
    }
    document.addEventListener("mousedown", closeWhenClickedOutside);
    return () =>
      document.removeEventListener("mousedown", closeWhenClickedOutside);
  }, [modalRef, setShowModal]);

  return (
    <div className="modal">
      <div className="modal-content" ref={modalRef}>
        <p>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusantium,
          nesciunt.
        </p>
      </div>
    </div>
  );
}

Here is a DEMO

2. Move to next input element on Enter

Usually, when we fill a form, to move to the next field we use a mouse and click on the area and then start typing.

But, what if we could move to next field when we hit Enter? Wouldn't that be cool! ๐Ÿคฏ

Fortunately, we can do that using useRef and it's pretty simple as well!

Let's get into action then. We'll create a simple App containing input and button elements, as show below and add some CSS to it as well.

image.png

App Js

export default function App() {

  function submitHandler() {
    alert("Submit Successful");
  }

  return (
    <div className="App">
      <h2>Login to Instagram</h2>
      <div className="wrapper">
        <input type="text" placeholder="username" className="user-input" />
        <input type="password" placeholder="password" className="user-input" />
        <button className="btn-submit">Submit</button>
      </div>
    </div>
  );
}
.App {
  font-family: sans-serif;
  text-align: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.wrapper {
  border: 2px solid;
  border-radius: 4px;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 100%;
  max-width: 350px;
}
.wrapper > * {
  margin: 0.5rem;
  border-radius: 4px;
  padding: 5px;
}
.user-input {
  display: block;
  border: 1px solid grey;
}

.user-input:focus,
.btn-submit:focus {
  outline: 3px solid greenyellow;
}

We also have a function submitHandler that alert when we hit Enter (not yet).

Now, let's store the reference of the both the inputs and the button as well using useRef.

Once, done, this is what our code looks like.

App Js

import "./styles.css";
import { useRef } from "react";

export default function App() {
  const userNameRef = useRef(null);
  const passwordRef = useRef(null);
  const submitBtnRef = useRef(null);

  function submitHandler() {
    alert("Submit Successful");
  }

  return (
    <div className="App">
      <h2>Login to Instagram</h2>
      <div className="wrapper">
        <input
          type="text"
          placeholder="username"
          className="user-input"
          ref={userNameRef}
        />
        <input
          type="password"
          placeholder="password"
          className="user-input"
          ref={passwordRef}
        />
        <button className="btn-submit" ref={submitBtnRef}>
          Submit
        </button>
      </div>
    </div>
  );
}

Logging our ref's, we can clearly see we got what we want. We have the references of all three elements.

image.png

Now, We'll run a useEffect once, so that we can put focus on our username field without the user having to make a click to access it.

App Js


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

Running this, our username input is now in focus on page render itself! Cool right. ๐Ÿ˜

Now, when user types and then hits Enter we need the focus to shift to password field.

To do this we'll write a simple function and attach it to onKeyDown event on the username input.

Once done, this is what or App Js file looks like and below is how it works.

App Js

import "./styles.css";
import { useRef, useEffect } from "react";

export default function App() {
  const userNameRef = useRef(null);
  const passwordRef = useRef(null);
  const submitBtnRef = useRef(null);

  function submitHandler() {
    alert("Submit Successful");
  }

  function passwordRefHandler(e) {
    if (e.key === "Enter") {
      passwordRef.current.focus();
    }
  }

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

  return (
    <div className="App">
      <h2>Login to Instagram</h2>
      <div className="wrapper">
        <input
          type="text"
          placeholder="username"
          className="user-input"
          ref={userNameRef}
          onKeyDown={passwordRefHandler}
        />
        <input
          type="password"
          placeholder="password"
          className="user-input"
          ref={passwordRef}
        />
        <button className="btn-submit" ref={submitBtnRef}>
          Submit
        </button>
      </div>
    </div>
  );
}

focus-one.gif

The focus shifted from username field to password field on enter. But how did that happen.

Well , we added onKeyDown event listener on username field and once the user hits enter, the condition in the passwordRefHandler is met and focus is added to password field.

We need to create similar function to move the focus to Submit button and when the button is in focus and then when the user hits Enter key, our data is submitted (not really) and alert is shown.

focus two.gif

That's it. As you can see we were able to fill and submit a form without having to touch the mouse.

CelebrationLoopGIF.gif

Try the DEMO

Wait, what if your Input or let's say any element that you want to reference is in another component.

You might come across this (at least I did) when the project is full of components. How would you deal with it then?

We could just pass it as a prop right? just like we pass data? Well no actually. If you try doing that React , it'll throw a warning, more importantly, our forward wont work.

image.png

How do we do it then? The answer is in the warning itself. Using forwardRef

3. What is forwardRef?

forwardRef is a basically a helper function from React that allows us to forward a ref created in component to another one.

It takes a callback , which take two arguments, one the props itself and the other one being ref that you want to forward.

Below is the syntax.

React.forwardRef((props, ref) => {})

Now that we at least know forwardRef a little, let's try to use it by creating a component and forwarding the ref to it.

Input Js


import React from "react";

function Input({ type, placeholder, onKeyDown }) {
  return (
    <input
      type={type}
      placeholder={placeholder}
      onKeyDown={onKeyDown}
      className="user-input"
    />
  );
}

export default Input;

Now lets update our App Js file and use the Input component.

App Js


import "./styles.css";
import { useRef, useEffect } from "react";
import Input from "./Input";

export default function App() {
  const userNameRef = useRef(null);
  const passwordRef = useRef(null);
  const submitBtnRef = useRef(null);

  function submitHandler() {
    alert("Submit Successful");
  }

  function passwordRefHandler(e) {
    if (e.key === "Enter") {
      passwordRef.current.focus();
    }
  }
  function submitRefHandler(e) {
    if (e.key === "Enter") {
      submitBtnRef.current.focus();
    }
  }

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

  return (
    <div className="App">
      <h2>Login to Instagram</h2>
      <div className="wrapper">
        <Input
          type="text"
          placeholder="username"
          ref={userNameRef}
          onKeyDown={passwordRefHandler}
        />
        <Input
          type="password"
          placeholder="password"
          ref={passwordRef}
          onKeyDown={submitRefHandler}
        />
        <button
          className="btn-submit"
          onKeyDown={submitHandler}
          ref={submitBtnRef}
        >
          Submit
        </button>
      </div>
    </div>
  );
}

Nice. Now that we have the basic setup lets try using forwardRef. We do this by wrapping our Input in forwardRef and then set the ref attribuite.


import { forwardRef } from "react";

function Input({ type, placeholder, onKeyDown }, ref) {
  return (
    <input
      ref={ref}
      type={type}
      placeholder={placeholder}
      onKeyDown={onKeyDown}
      className="user-input"
    />
  );
}

const forwardInput = forwardRef(Input);

export default forwardInput;

That's it! It works!.

DancingBabyDanceMovesGIF.gif

Try the DEMO

So, what's the conclusion then?

useRef is a great hook. It allows us to do things that otherwise wouldn't have been possible to do with React.

Summary

  • useRef is a hook that returns an object with a single property called current
  • current holds the reference of the element that we tell the ref to point to.
  • useRef does not cause are-render when updated.
  • The reference does not change between renders.
  • forwardRef allows us to pass ref made in a component to another component.
  • useRef was designed to access and store references of DOM elements but we can also use it to store any data types like Object, strings etc.

UhThatsAllIGuessRandyMarshGIF.gif

DEMO LINKS

Hope you found this helpful in someway.

Thanks for reading.

Happy Coding.

References

ย