A Guide for Refs in React

oussel

oussel

Posted on February 18, 2021

A Guide for Refs in React

Hello!

In this article, we're going to talk about refs in React. This is a relatively well-known and widely used concept of React that makes life a lot easier in some cases. but at the same time, we should try to avoid using them if possible. Because it can enter into conflict with React’s diff and update approaches.

What we will see in this article :

What are refs? :

As the documentation mentioned :

“ Refs provide a way to access DOM nodes or React elements created in the render method ”

For Example, you can focus an input node based on a button click :

Alt Text

style.css
input:focus {
  background-color: Aqua;
}
Enter fullscreen mode Exit fullscreen mode
MyComponent.js

import React from 'react';
import '.style.css'

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.inputRef= React.createRef();
  }

  setFocus = () => {
    this.inputRef.current.focus();
  };

  render() {
    return (
      <div>
        <input ref={this.inputRef} />
        <button onClick={this.setFocus}>Click to set focus !</button>
      </div>
    );
  }
}

export default MyComponent;

Enter fullscreen mode Exit fullscreen mode

What happened exactly?

In fact when you include < MyComponent /> JSX syntax inside your react app, and at the time of rendering, React will first create an instance of the Class MyComponent and will call the constructor to construct the object instance after that he will call render method, this method tell React that you want to associate the ref with the inputRef that we created in the constructor. The input Node will then say to MyComponent instance "ok, I will assign to your attribute inputRef my address in memory, so you can have access to me later". And then when we click the button, our instance < MyComponent /> already knows the place of the input DOM node in memory then it can have access to all methods and attributes of this input DOM node ...

Using Refs is a different way to the typical React dataflow; normally in React DataFlow parent components interact with their children using props and React docs always inform you to stay as much as possible relying on this workflow but in few cases where you need to imperatively modify a child outside of the typical dataflow and have direct access to this child component to take for example its position, then you can use Refs ...

What are the different approaches to creating Refs ? :

In old versions of React, you can refer to a component with strings refs but now it's considered as legacy and they recommend using either the callback Ref or the object Ref.

  • Ref object: that you can create with createRef API (from React 16.3) or useRef Hook (from React 16.8) :

A ref object is a plain JS object that contains a current property: { current: < some value > }. this property is used to store a reference to the DOM node.

In the example above, if we console log this.inputRef :
Alt Text
You will see that our ref.current contains The input node Element, with that you can access all its methods like focus(), blur(), click() …

You can create a Ref Object with CreateRef API inside ClassComponent or UseRef Hook inside functional components.

But is there any difference between the two (CreateRef API vs UseRef Hook)?

Ofc you can't use Hooks in general inside a class component, React will not let you do that. But if you try to use CreateRef API inside your functional component a new object Ref will be created in every rerender and you will lose your old object ref.

In fact React.createRef(initValue) and useRef(initValue) both returns an object ref { current: initValue } Besides that useRef also memoizes this ref to be persistent across multiple renders in a functional component. because In React you cannot create an instance from a functional component. and if we do not have an instance, we, therefore, do not have a direct way to persist this reference across multiple renders. that's Why in general some hooks come up to help us and make our functional components stateful and more powerful throughout their lifecycle.

And That’s why It is sufficient to use React.createRef in class components, as the ref object is assigned to an instance variable in the constructor, hence accessible throughout the component and its lifecyle.

  • Callback ref:

Another way to set refs in React is to use callback refs. Callback refs is just a function that, when called, receives the React component instance or HTML DOM node as its argument, which can be stored and accessed elsewhere.

if we use callback ref in the first example, this is how it will look like :

MyComponent.js
//...
class MyComponent extends React.Component {
  callbackFunction = (node) => {
    this.inputRef = node; // this callback will attach node to inputRef
  };
  setFocus = () => {
    this.inputRef.focus(); // we access node directly , no need to current property unlike Object ref
  };

  render() {
    return (
      <div>
        <input ref={this.callbackFunction} />
        <button onClick={this.setFocus}>Focus Input</button>
      </div>
    );
  }
}

export default MyComponent;

Enter fullscreen mode Exit fullscreen mode
When does the callback get called?

React docs are very clear on this:

“ React will call the ref callback with the DOM element when the component mounts, and call it with null when it unmounts. Refs are guaranteed to be up-to-date before componentDidMount or componentDidUpdate fires.”

Is there any advantage of using one over the other (Object Ref vs Callback Ref)?

the Docs say :

“Callback refs give you more fine-grain control”

This means that with Callback refs you gain more flexibility, you can look at this interesting example that can help you for example to set multiple refs in an array :

class A extends React.Component {
    constructor(props) {
        super(props);
        this.inputs = [];
    }

    render() {
        return [0, 1, 2, 3].map((key, index) => (
            <Input 
                key={key} 
                ref={input => this.inputs[index] = input}
            />)
        );
    }
}

Enter fullscreen mode Exit fullscreen mode

Another advantage of Callback Ref also mentioned in useRef docs :

“If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.”

Meaning; if you want to attach a ref to a component that will mount later or depending on a conditional (using conditional rendering) then you can use the callback ref. because it can attach a ref to your DOM node dynamically.

The best example for this is from the docs itself:

link here in this part https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node

How can I use Refs and to What can I refer?

You can refer a ref to the ref attribute of two elements: a DOM node (like div, input …), or React Class Component but you may not use the ref attribute on functional components because they don’t have instances. This means :

- on DOM node you can refer for example to a div or input (first example) like this :

<div ref={myRef} />
Enter fullscreen mode Exit fullscreen mode

And you can use this reference to focus for example input text or get the position of a div.

- on React Class component you can do it like this :

import React from "react";

import "./styles.css";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.myComponentRef = React.createRef();
  }

  setFocusOnMyChild = () => {
    this.myComponentRef.current.setFocus(); // As you can see we are invoking SetFocus
    //that is inside MyComponent from outSide .
  };
  render() {
    // myComponentRef refer to MyComponent React Class instance
    return (
      <div>
        <MyComponent ref={this.myComponentRef} />
        <button onClick={this.setFocusOnMyChild}>
          Im a button from parent (App.js)
        </button>
      </div>
    );
  }
}

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }
  setFocus = () => {
    this.inputRef.current.focus();
  };
  render() {
    return (
      <div>
        <input ref={this.inputRef} />
        <button onClick={this.setFocus}>
          Click to set focus ! (Im inside MyComponent)
        </button>
      </div>
    );
  }
}

export default App;

Enter fullscreen mode Exit fullscreen mode
result:

Alt Text

By referring to a class component you can have access to methods inside the instance of this Class when React Creates it and invoke them from outside. you can console.log(classRef) and see all information that you can take from there.

- But you can't do the same with React Functional component: your ref object will be null, Why? :

Because functional components as we mentioned before, don’t have an instance in React, An instance is what you refer to as this in the component class you write. It is useful for storing local state and reacting to the lifecycle events.

If you want to pass ref to your functional component, you can do it with the help of the hook useImperativeHandle combined with RefForward

this can help you to refer to a functional component and you can for example invoke some functions that are inside your functional component from outside. these functions are exposed with the help of the useImperativeHandle hook, the same way you do it before with the Class component,

in fact, the useImperativeHandle hook will customize the instance that is exposed to the parent component when using ref. and the forwardRef will help us to transfer the ref between parent and child.

Thankfully React documentation is very rich with examples, you can check it here :

PS: We are discussing here referring to a functional component not using Refs inside functional component because. You can create and use refs inside a functional component as long as you refer to a DOM element or a class component.

How to pass a single or multiple ref/refs to a child component?

- Passing a single ref :

It's simple you can do it with RefForward. As we mentioned before RefForward is a technique that helps us to pass automatically refs (in other words, “forward” it) to a child component either for Class component or functional component.

React.forwardRef takes a function with props and ref arguments. This function returns a JSX Element.

React.forwardRef((props, ref) => {
    ...
})
Enter fullscreen mode Exit fullscreen mode

We create for example a CustomTextInput with the Help of React.forwardRef like this :

const CostumTextInput = React.forwardRef((props, ref) => (
  <input type="text" placeholder={props.placeholder} ref={ref} />
));
Enter fullscreen mode Exit fullscreen mode

You can now get a ref directly to the DOM node input and also pass as props your placeholder :

const ref = React.createRef();
<CostumTextInput ref={ref} placeholder="Hello" /> ;
Enter fullscreen mode Exit fullscreen mode

If you don’t want to use React.forwardRef, you can pass ref as a prop with a different name (!= ref) to Child Component, and there is no problem with that. Even React docs mention the custom ref prop as a more flexible approach to React.forwardRef :

“If you use React 16.2 or lower, or if you need more flexibility than provided by ref forwarding, you can use this alternative approach and explicitly pass a ref as a differently named prop.”

But you should pay attention if you pass an inline callback ref function down as prop because callback can trigger a re-render unless you have used a way of memoization with help of useCallback for example.

The only advantages of forwardRef API :

  • consistent api for refs and uniform access API for DOM nodes, functional and class components
  • ref attribute does not bloat your props, because when you use forwardRef , it gives you a second argument ref, it didn’t add ref to your props

- Passing mupltiple refs :

You can do it with useImperativeHandle hook and RefForward API, like this :
import "./styles.css";
import React,{ useRef ,useImperativeHandle} from "react";

export default function App() {
  const inputsRef = useRef(null);

  //inputsRef will Containt inside current property
  //an costum instance that contains all methods exposed with useImperativeHandle ,thanks to  forwardRef and useImperativeHandle

  return (
    <div className="App">
      <Inputs ref={inputsRef} />
      <button onClick={() => inputsRef.current.focusMyInput1()}>Focus Input1</button>
      <button onClick={() => inputsRef.current.focusMyInput2()}>Focus Input2</button>
      <button onClick={() => inputsRef.current.focusMyInput3()}>Focus Input3</button>
    </div>
  );
}

const Inputs = React.forwardRef((props,ref)=>{

  //let's create a ref for each input
  const refInput1 = useRef();
  const refInput2 = useRef();
  const refInput3 = useRef();

  //Let's Expose a costum instance to the Parent Component 
  //this instance will contain all methods to invoke focus on inputs

  //a parent component that renders <Inputs ref={inputsRef} /> 
  //would be able to call all methods (focusMyInput1,focusMyInput2,focusMyInput3).

  useImperativeHandle(ref, () => ({
    focusMyInput1: () => {
      refInput1.current.focus();
    } ,
    focusMyInput2: () => {
      refInput2.current.focus();
    } ,
    focusMyInput3: () => {
      refInput3.current.focus();
    } 
  }));


  return (
    <div className="Inputs">
      <input ref={refInput1} />
      <input ref={refInput2} />
      <input ref={refInput3} />
    </div>
  );
})
Enter fullscreen mode Exit fullscreen mode
Another way to pass multiple refs to child component: You can construct an object of Refs, and passe it as props with a prop that has a different name to "ref" to a Child Component, Like this :

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

export default function App() {
  const refInput1 = useRef(null);
  const refInput2 = useRef(null);
  const refInput3 = useRef(null);
  //We are passing here multiple Refs with the help of Props AllRefs
  //AllRefs is just a simple prop that receive an object of refs that after will be associated to an input node dom
  return (
    <div className="App">
      <Inputs allRefs={{ refInput1, refInput2, refInput3 }} />
      <button onClick={() => refInput1.current.focus()}>Focus Input1</button>
      <button onClick={() => refInput2.current.focus()}>Focus Input2</button>
      <button onClick={() => refInput3.current.focus()}>Focus Input3</button>
    </div>
  );
}

function Inputs(props) {
  return (
    <div className="Inputs">
      <input ref={props.allRefs.refInput1} />
      <input ref={props.allRefs.refInput2} />
      <input ref={props.allRefs.refInput3} />
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode
  • Result in Both methods :

Alt Text

That's all. And Remember Don’t Overuse Refs, Hope you learned something new.

💖 💪 🙅 🚩
oussel
oussel

Posted on February 18, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related