Using refs in React functional components (part 1) - useRef + callback ref
Tags: react, javascript
Published: 2021-01-10 00:02:25

Hello everyone! 👋

Lately, I have been working a bit with refs in functional components and decided to go beyond the surface and dig a little deeper into it. Also, I decided to start writting as a way to improve my knowledge since you don't really understand something until you explain it.

That's how the idea of this series came! It won't be any complete guide of the Ref API but rather an overview of it based on what I understood while studying it to be more confident when using it in the future.

Since these are my first articles any feedback will be valuable. Hopefully, it will be helpful to you too.

If you want to check, I also put the code for these examples on [github](https://github.com/CarlosRafael22/refs-study).

Without further ado, let's go!


# 1. What are refs?

Refs are simply references to anything, like a DOM node, Javascript value, etc. To create a ref in a functional component we use the `useRef()` hook which returns a mutable object with a `.current` property set to the initialValue we passed to the hook.

```javascript
const ref = useRef(null); // ref => { current: null }
```

This returned object will persist for the full lifetime of the component. Thus, throughout all of its re-rendering and until it unmounts.


**There are basically two use cases for refs in React**:
* Accessing underlying DOM nodes or React Elements
* Creating mutable instance-like variables for functional components

In the following sections and next posts I will try to cover some use cases with examples of common scenarios.

# 2. Accessing DOM nodes in the same React component

To create a reference to a DOM node in a component we can do it either using the `useRef()` hook, which for most cases is the easier and best approach, or using the `callback ref` pattern which gives you more control when refs are set and unset.

Let’s see how they compare in an example where there are two buttons, one which sets focus on the input and another which logs the value the user typed in the input.

### 2.1 useRef()

```javascript
import React, { useRef } from 'react';

const SimpleRef = () => {
    const inputRef = useRef<HTMLInputElement>(null);

    const onClick = () => {
        console.log('INPUT VALUE: ', inputRef.current?.value);
    }

    const onClickFocus = () => {
        console.log('Focus input');
        inputRef.current?.focus();
    }

    return (
        <div>
            <input ref={inputRef} />
            <button onClick={onClick}>Log value</button>
            <button onClick={onClickFocus}>Focus on input</button>
        </div>
    );
};
```

The `useRef<HTMLInputElement>(null)` returns an `{ current: null }` object initially since we provided null as the initialValue. After we associate it to the `<input>`, with its ref attribute, we can access the `HTMLInputElement` and its properties through the `.current` property of the ref.

With that, when the user clicks the first button we log the input value the user typed and when he/she clicks in the second button we call the `focus()` method from the `<input>` element.

Since in this project I am using Typescript we have to set the type of the ref we are storing. As we are putting the ref on an `<input>`, we define it as a `HTMLInputElement` and use the [optional chaining](https://javascript.info/optional-chaining) to prevent an error while accessing the properties of the ref.

### 2.2 Callback Ref

This is another way React supports to set refs. Instead of passing a ref attribute created by `useRef()`, you pass a function. As stated in the docs, the function receives the React component instance or HTML DOM element as its argument, which can be stored and accessed elsewhere.

There is a small difference when creating the same example with a callback ref .

```javascript
const SimpleCallbackRef = () => {
    let inputRef: HTMLInputElement | null;

    const onClick = () => {
        console.log('INPUT VALUE: ', inputRef?.value);
    }

    const onFocusClick = () => {
        console.log('Focus input');
        inputRef?.focus();
    }
    console.log('Rendering')
    return (
        <div>
            <input ref={node => { inputRef = node; }} />
            <button onClick={onClick}>Log value</button>
            <button onClick={onFocusClick}>Focus on input</button>
        </div>
    );
};
```
We simply set the ref attribute in the `<input>` with a function instead of ref attribute created by `useRef()`. This function receives the DOM node and assigns it to the `inputRef` we declared before. Since we didn't create a ref with useRef the `inputRef` variable stores the DOM element itself then we don't need to access the `.current` property, as you can see in the onClick and onFocusClick functions.

However, note that we start by setting the type of `inputRef` as either a `HTMLInputElement` or null.

**_Why was that?_** This is due to a caveat when using callback ref. As stated in the docs: when it’s defined as an inline function, it will be called twice on updates, first with null and then again with the DOM element.

So Typescript warns that the `inputRef` variable can be null(since the node also can be) then after typing like this Typescript won't complain.
To deal with this caveat, in this example, we can either do this or ensure that we will only assign the node to the inputRef when the node is valid:

```javascript
let inputRef: HTMLInputElement;
// ... the same code
<input ref={node => { 
    console.log('Attaching node: ', node)
    if (node) { // with this we know node is not null or undefined
        inputRef = node;
    }
}} />
```
This example was only made to illustrate the difference between how to use the callback ref and useRef. In such a simple case, using callback ref only gives us unnecessary work so I would go with useRef().

### 2.3 The callback ref pattern caveat
Still on this caveat and how to deal with it. Getting it straight from the docs:
> If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element. This is because a new instance of the function is created with each render, so React needs to clear the old ref and set up the new one. You can avoid this by defining the ref callback as a bound method on the class, but note that it shouldn’t matter in most cases.

To illustrate this callback ref caveat better, see the example below:

```javascript
import React, { useState } from 'react';

const SimpleCallbackRefRerender = () => {
    let inputRef: HTMLInputElement;
    const [count, setCount] = useState(0);

    const onClick = () => {
        console.log('INPUT VALUE: ', inputRef?.value);
    }

    const onFocusClick = () => {
        console.log('Focus input');
        inputRef?.focus();
    }

    const onRerenderClick = () => {
        console.log('Clicked to re-render');
        setCount(count+1);
    }

    return (
        <div>
            <input ref={node => { 
                console.log('Attached node: ', node)
                if (node) {
                    inputRef = node;
                }
             }} />
            <button onClick={onClick}>Log value</button>
            <button onClick={onFocusClick}>Focus on input</button>
            <button onClick={onRerenderClick}>Re-render count {count}</button>
        </div>
    );
};
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dtp4yvojgw6gu27pgchl.PNG)

As you can see in the logs, when first rendering, the `callback ref` had the `HTMLInputElement` node to be passed to the ref attribute of the `<input>`. However, when clicking on the button to re-render, the node was first null and then it was the actual element again.

This happens because when the component is being re-rendered it first unmounts then React calls the callback ref passing null to it to clear the old ref and when it mounts again then React calls the callback ref with the DOM element. To deal with that, in the callback ref we can check whether the node is not null/undefined and then assign to the `inputRef` variable, as we did.

# 3. Accessing dynamically added DOM elements
**_Cool, I got that! But why am I going to use callback ref?_**
Well, even though there is the `useRef()` hook which covers most of the common cases we would need for a ref, the `callback ref` pattern provides us with a more powerful way to have control for cases when a child is being added or removed dynamically, does not have the same lifetime as the parent or you need to perform any effect when a ref has been mounted.

Let’s consider a simple example where part of a form only appears when the user clicks in the first button and we when it happens we want the newly shown input to be focused.

```javascript
import React, { useState, useRef } from 'react';

const CallbackRefDynamicChild = () => {
    const inputRef = useRef<HTMLInputElement>(null);
    const [visible, setVisibility] = useState(false);

    const onClick = () => {
        console.log('INPUT VALUE: ', inputRef.current?.value);
        setVisibility(true);
    }

    const onFocusClick = () => {
        console.log('Focus on first input');
        inputRef.current?.focus();
    }

    const callbackRef = (node: HTMLInputElement) => {
        console.log('Attached node: ', node);
        if(node) {
            node.focus();
        }
    }

    console.log('Rendering: ', inputRef);
    return (
        <div>
            <input ref={inputRef} />
            <button onClick={onClick}>Unlock next input</button>
            {visible && (
                <>
                <input ref={callbackRef} />
                <button onClick={onFocusClick}>Focus on first input</button>
                </>
            )}
        </div>
    );
};
```

Since the second input is added dynamically, when the state changes and the visible variable is set to true, the best approach for this is using the `callback ref`. 

The `useRef` doesn’t notify you when its content changes. Mutating the `.current` property doesn’t cause a re-render. Therefore, to run any effect when React attaches or detaches a ref to a DOM node, we would need to use the callback ref.

With `callback ref`, when the second input shows up and the ref is attached to the `<input>`, the `callbackRef` function is called with the `HTMLInputElement`. Then if the node is not null/undefined we call the `focus()` method to achieve what we wanted.

# 4. Conclusion

In this first part of the series, we covered possible ways to use refs in functional components for the case where we want to access DOM nodes in the same component.

In the next posts we will see how to use refs to access other React components and also to have an instance-like variable in functional components.

If you've came this far, I would really appreciate any feedback or comments pointing any corrections you would suggest. Hopefully this will be helpful to you :)

# 5. References

This series would not be possible without other articles from awesome developers out there. If you want check what helped my learning, click on the links below:

https://moduscreate.com/blog/everything-you-need-to-know-about-refs-in-react/
https://blog.logrocket.com/how-to-use-react-createref-ea014ad09dba/
https://www.robinwieruch.de/react-ref
https://medium.com/trabe/react-useref-hook-b6c9d39e2022
https://elfi-y.medium.com/react-callback-refs-a-4bd2da317269
https://linguinecode.com/post/how-to-use-react-useref-with-typescript
https://reactjs.org/docs/refs-and-the-dom.html