DEV Community

Cover image for Using refs in React.js
Amod Shinde
Amod Shinde

Posted on

Using refs in React.js

In this article, we are going to investigate why React.js, a library that keeps your code away from DOM manipulation, keeps its doors open for you to access. React rethinks a view as a result of a state of a component. It provides JSX, a syntactic sugar over Javascript, to design the view layer and then modifies the DOM itself rather than giving the control to the developer.

Still, the React team provided escape routes and kept the library open for certain situations that go beyond the scope of what React is designed for.

Creating refs

Refs are escape routes and it's better to avoid them whenever possible. When we obtain a DOM element using ref and then later modify its attribute, we might enter into a conflict with React's diff and update approach.

Let's start with a simple component and grab a DOM element using ref, assuming that you already know how to set up a basic react app.

import React, { useRef } from 'react'

function Button ({ label, action }) {
        // declare & initializing a reference to null
   const buttonRef = useRef(null)

   // attaching 'buttonRef' to the <button> element in JSX
    return (
      <button onClick={action} ref={buttonRef}>{label}</button>
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

In the above piece of code, we are using a react hook 'useRef' to create and initialize a variable called buttonRef. We then assign buttonRef to ref attribute on button JSX element.

Using React refs

As we discussed earlier in this article we are declaring views based on the state, and though we are still altering the state using functions, we are not in direct control of the DOM changes. But in a few use cases, it makes sense to introduce refs in your code.

Focus Control

To better understand the problem statement let's storify the situation.

Arjun is a software development intern at Doogle INC and his manager has given him the task of creating contact forms. The manager has asked him to focus on the first input element in the form when a modal is opened Arjun is confused about how he can achieve this in React.js. Let's help Arjun out.

import React, { useState } from "react";

const InputModal = ({ close }) => {
  const [value, updateVal] = useState("");
  const onChange = (e) => {
    updateVal(e.target.value);
  };

  const onSubmit = (e) => {
    e.preventDefault();
    close();
  };

  return (
    <div className="overlay">
      <div className="modal">
        <h1>Insert a new value</h1>
        <form action="?" onSubmit={onSubmit}>
          <input type="text" onChange={onChange} value={value} />
          <button>Save new value</button>
        </form>
      </div>
    </div>
  );
};

export default InputModal;
Enter fullscreen mode Exit fullscreen mode

The first thing we need to do is to get a reference to the input.

import React, { useState, useRef } from "react";

const InputModal = ({ close }) => {
  const [value, updateVal] = useState("");
  const inputRef = useRef(null);

  const onChange = (e) => {
    updateVal(e.target.value);
  };

  const onSubmit = (e) => {
    e.preventDefault();
    close();
  };

  return (
    <div className="overlay">
      <div className="modal">
        <h1>Insert a value</h1>
        <form action="?" onSubmit={onSubmit}>
          <input type="text" onChange={onChange} value={value} ref={inputRef} />
          <button>Save</button>
        </form>
      </div>
    </div>
  );
};

export default InputModal;
Enter fullscreen mode Exit fullscreen mode

Next, when our modal loads, we imperatively call focus on our input ref.

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

const InputModal = ({ close }) => {
  const [value, updateVal] = useState("");
  const inputRef = useRef(null);

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

  const onChange = (e) => {
    updateVal(e.target.value);
  };

  const onSubmit = (e) => {
    e.preventDefault();
    close();
  };

  return (
    <div className="overlay">
      <div className="modal">
        <h1>Insert a value</h1>
        <form action="?" onSubmit={onSubmit}>
          <input type="text" onChange={onChange} value={value} ref={inputRef} />
          <button>Save</button>
        </form>
      </div>
    </div>
  );
};

export default InputModal;
Enter fullscreen mode Exit fullscreen mode

Note: You need to access the element through the current property of the ref you declare.

Follow this link to check the working code. Try commenting out inputRef implementation and see how the input focus changes with and without ref.

Detect if an element is contained

Similarly, we would want to take an action in the app when an event is dispatched. Like close the modal when the user clicks outside of it.

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

const InputModal = ({ close }) => {
  const [value, updateVal] = useState("");
  const inputRef = useRef(null);
  const modalRef = useRef(null);

  const onClickOverlay = (e) => {
    const overlay = e.target;
    if (modalRef.current && !modalRef.current.contains(overlay)) {
      e.preventDefault();
      e.stopPropagation();
      close();
    }
  };
  useEffect(() => {
    inputRef.current.focus();
    document.body.addEventListener("click", onClickOverlay);
  }, []);

  const onChange = (e) => {
    updateVal(e.target.value);
  };

  const onSubmit = (e) => {
    e.preventDefault();
    close();
  };

  return (
    <div className="overlay">
      <div className="modal" ref={modalRef}>
        <h1>Insert a value</h1>
        <form action="?" onSubmit={onSubmit}>
          <input type="text" onChange={onChange} value={value} ref={inputRef} />
          <button>Save</button>
        </form>
      </div>
    </div>
  );
};

export default InputModal;
Enter fullscreen mode Exit fullscreen mode

Here, we are checking if the user click is out of the modal ref limit. If it is we are calling close() function from props to close the modal.

Integrating DOM-based libraries

Like React, there are other utilities and libraries outside its ecosystem that have been in use for years. To use such libraries refs come in handy.

GreenSock library is a popular choice for animation examples. To use it, we need to send a DOM element to any of its methods.

Let’s go back to our modal and add some animation

import React, { useState, useRef, useEffect } from "react";
import gsap from "gsap";

const InputModal = ({ close }) => {
  const [value, updateVal] = useState("");

  const inputRef = useRef(null);
  const modalRef = useRef(null);
  const overlayRef = useRef(null);

  const onComplete = () => {
    inputRef.current.focus();
  };
  const gaspTimeline = gsap.timeline({ paused: true, onComplete });

  const onClickOverlay = (e) => {
    const overlay = e.target;
    if (modalRef.current && !modalRef.current.contains(overlay)) {
      e.preventDefault();
      e.stopPropagation();
      close();
    }
  };
  useEffect(() => {
    //timeline - gasp
    gaspTimeline
      .from(overlayRef.current, {
        duration: 0.25,
        autoAlpha: 0
      })
      .from(modalRef.current, {
        duration: 0.25,
        autoAlpha: 0,
        y: 25
      });

    gaspTimeline.play();

    document.body.addEventListener("click", onClickOverlay);
  }, []);

  const onChange = (e) => {
    updateVal(e.target.value);
  };

  const onSubmit = (e) => {
    e.preventDefault();
    close();
  };

  return (
    <div className="overlay" ref={overlayRef}>
      <div className="modal" ref={modalRef}>
        <h1>Insert a value</h1>
        <form action="?" onSubmit={onSubmit}>
          <input type="text" onChange={onChange} value={value} ref={inputRef} />
          <button>Save</button>
        </form>
      </div>
    </div>
  );
};

export default InputModal;
Enter fullscreen mode Exit fullscreen mode

**Here is the working demo.**

Forwarding Refs

Refs are useful for specific actions. The examples shown are a little simpler than what we usually find in a real-life web application. We need to deal with complex components and we barely use plain HTML elements directly. It's common to use a ref from one component in another component.

import React from 'react'

const LabelledInput = (props) => {
  const { id, label, value, onChange } = props

  return (
    <div class="labelled--input">
      <label for={id}>{label}</label>
      <input id={id} onChange={onChange} value={value} />
    </div>
  )
}

export default LabelledInput
Enter fullscreen mode Exit fullscreen mode

The issue now is that passing a ref to this component will return its instance, a React component reference, and not the input element we want to focus on like in our first example.

React provides forwardRef, which allows you to define internally what element the ref will point at.

import React from 'react'

const LabelledInput = (props, ref) => {
  const { id, label, value, onChange } = props

  return (
    <div class="labelled--input">
      <label for={id}>{label}</label>
      <input id={id} onChange={onChange} value={value} ref={ref}/>
    </div>
  )
}

export default React.forwardRef(LabelledInput)
Enter fullscreen mode Exit fullscreen mode

Now, when a parent component passes a ref value, it’s going to obtain the input, which is helpful to avoid exposing the internals and properties of a component and breaking its encapsulation.

Originally posted on amodshinde.com

Discussion (6)

Collapse
jukkahuuskonen profile image
Jukka Huuskonen

This seems to discuss only refs to document elements. You left out the other half of useRef-usage: when you need to have variables in eg. useEffects-hook that may change during effects lifespan, but you don't want to re-run the hook when that value changes. Good example is when you want to do something when the component unmounts.

const valueAtUnmountRef = useRef();

  useEffect(() => {
    yourFunctionToRunOnUnmount(valueAtUnmountRef.current);
  }, []);
Enter fullscreen mode Exit fullscreen mode

Now in components lifetime, you can update the valueAtUnmountRef.current and be assured that yourFunctionToRunOnUnmount has correct value as parameter when component unmounts.

Collapse
amoled27 profile image
Amod Shinde Author

Thanks a lot for contributing to my knowledege \m/

Collapse
lukeshiru profile image
LUKESHIRU

The idea with element refs is to use them mainly when interfacing with external libraries that don't interact with React directly. So, for example, using it to get the current value of an input or to focus an input are both bad usages of ref. To get the current value, you can just turn your input into a controlled element, and to focus you can just use autoFocus. So instead of doing this:

const inputRef = useRef();

useEffect(() => inputRef.current?.focus());

return (
    <form onSubmit={() => send(inputRef.value)}>
        <input ref={inputRef} />
    </form>
);
Enter fullscreen mode Exit fullscreen mode

Yo do this:

const [value, setValue] = useRef();

return (
    <form onSubmit={() => send(value)}>
        <input
            autoFocus
            value={value}
            onChange={({ currentTarget: { value } }) => setValue(value)}
        />
    </form>
);
Enter fullscreen mode Exit fullscreen mode

The short version is: Don't use refs for DOM interactions with elements you control, and instead just use them when you want to interact with external libraries, or when you want to store a value without triggering re-renders.

Cheers!

Collapse
amoled27 profile image
Amod Shinde Author

Thanks, totally agree with you. I just gave a sort of demo on how refs work. In simple words:
There are controlled and uncontrolled components in React. Libraries or external files which we may use in React come under uncontrolled components.
As the name says, we dont have a control over it via React app, hence we cant use traditional document.getElement queries to manipulate DOM.
Thus, react refs come into the picture to access and manipulate these uncontrolled components.

Collapse
abesamma profile image
A.B. Samma

Thanks for this article. Good eye opener.

Collapse
aderchox profile image
aderchox

Very nicely written, thank you.