Using refs in React functional components (part 2) - forwardRef + useImperativeHandle
Tags: react, javascript
Published: 2021-01-10 00:02:34

Hello everyone! 👋

Continuing the series about refs in functional components, in this article we will cover another case we need refs: when accessing other functional components.

For this article we will understand a bit more about [Ref Forwading](https://reactjs.org/docs/forwarding-refs.html) and `useImperativeHandle`, an extra hook that lets us customize the ref the parent component will have access.

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

So let's jump into that!


# 1. Accessing functional components with refs

In all the previous examples, in the first part of this series, we needed to access a DOM element in the same component, but what if we need to access an element from a child component, how would we do that?

### 1.1 Ref forwarding

As stated in the docs, React components hide their implementation details, including their rendered output. Thus, components cannot easily access refs from their children.

Although this is a good thing, preventing us from relying on other component’s DOM structures, there are cases where we need to access a child’s DOM node for managing focus, selection and animation, for example.

To do that, React provides a feature called Ref Forwarding.
> Ref forwarding is an opt-in feature that lets some components take a ref they receive, and pass it further down (in other words, “forward” it) to a child.

To understand it, let's consider a simple example where a parent component wants to have a ref to a child’s input to be able to select its text when clicking on a button:

```javascript
import React from 'react';

type ForwardedInputProps = {
    placeholder?: string
};

const ForwardedInput = React.forwardRef<HTMLInputElement, ForwardedInputProps>(({ placeholder }, ref) => (
    <input ref={ref} placeholder={placeholder} />
));

const SimpleForwardRef = () => {
    const inputRef = React.useRef<HTMLInputElement>(null);

    const selectText = () => {
        inputRef.current?.select();
    }

    return (
        <div>
            <ForwardedInput ref={inputRef} placeholder="Type here"/>
            <button onClick={selectText}>Select text</button>
        </div>
    );
};
```

As you can see, we created a ref object with useRef in the parent component and passed it to the child component. In the `ForwardedInput` component we call the `React.forwardRef` function, which receives props and the ref passed to the functional component and returns the JSX.Element for it.
`ForwardedInput` uses the `React.forwardRef` to obtain the `ref` passed to it, so we can forward the ref down to the DOM input. This way, the parent component can get a ref to the underlying input DOM node and access it through its `inputRef` `current` property.

One important point to note is the typing in the `React.forwardRef`. As a generic function, it receives type parameters for the ref and the props but in the reversed order from its function parameters. Since we attached the forwarded ref to an <input> its type will be `HTMLInputElement`.


### 1.2 useImperativeHandle

In some more advanced cases you may need to have more control over the returned ref the parent will have access to. Instead of returning the DOM element itself, you explicitly define what the return value will be, adding new properties for the returned ref, for example.

In such cases you would need to use a special hook, the `useImperativeHandle`. As stated in the docs:
> useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with forwardRef:

Let’s understand it a bit better. Consider the following example where when the user clicks the button associated with the box it scrolls to the top of the box.

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

type BoxProps = {
    size: string,
    color: string
}

type IncrementedRef = {
    getYLocation: () => number | undefined,
    current: HTMLDivElement | null
}

const Box = forwardRef<IncrementedRef, BoxProps>(({size, color}, ref) => {
    const divRef = useRef<HTMLDivElement>(null);
    useImperativeHandle(ref, () => ({
        getYLocation: () => divRef.current?.getBoundingClientRect().top,
        current: divRef.current
    }));

    return (
        <div style={{
            height: size,
            width: size,
            backgroundColor: color,
            margin: '0 auto'
        }}
        ref={divRef}></div>
    );
});

const ImperativeHandleExample = () => {
    const refs = [useRef<IncrementedRef>(null), useRef<IncrementedRef>(null), useRef<IncrementedRef>(null)];

    const goToBox = (position: number) => {
        console.log('Go to box: ', refs[position].current?.current)
        const boxTop = refs[position].current?.getYLocation();
        window.scrollTo({ top: boxTop, behavior: 'smooth'})
    }

    return (
        <>
        <div>
            <button onClick={() => goToBox(0)}>Go to 1st box</button>
            <button onClick={() => goToBox(1)}>Go to 2nd box</button>
            <button onClick={() => goToBox(2)}>Go to 3rd box</button>
        </div>
        <Box size='500px' color='red' ref={refs[0]} />
        <Box size='500px' color='blue' ref={refs[1]} />
        <Box size='500px' color='green' ref={refs[2]} />
        </>
    );
};
```

Here, the Box component is wrapped with a `forwardRef` since we are receiving a ref from the parent. But instead of attaching it to the `<div>`, we are explicitly changing its return to the parent with the `useImperativeHandle` and attaching a new internal ref to the `<div>`.
**_Why so complex?_** Because we want to provide the ref to the parent component with the coordinate of the top of this `<div>`.

Since we want more control over what properties the parent will access from the ref we have the `useImperativeHandle` to set this new `getYLocation` function and the `<div>` as its `current` property. The `getYLocation` could simply be the value but I put as function to exemplify another way to a have a property.

Remember that with `useImperativeHandle` you have to explicitly state what the return of the `ref` will be. It won't contain any other property so if you didn't set it as the `current` property you wouldn't have access to the `<div>` itself in the parent component.

So, in the parent component we create refs and forward it to each Box component. When the user clicks on each button it will call `goToBox()` and with its position parameter we get the corresponding ref in the array of refs. Then, with the `getYLocation` function we defined with `useImperativeHandle` we have the Y coordinate of its top and scroll to it. The console.log prints the `<div>` from the ref’s `current` property to show that this way we have access to the element.

One last point is the typing again. The ref type passed to the forwardRef function **is not** a `HTMLDivElement` because with the `useImperativeHandle` we are creating a new return to be the ref and this new `ref` has only the `getYLocation` and `current` properties.


# 2. Conclusion

As shown in the above examples, you can also access underlying DOM elements from children functional components with the `Ref forwarding` feature. For more advanced cases, you can even customize what the parent component will have access with the `ref` passed to its children with the `useImperativeHandle`even though, as stated in the docs, imperative code using refs should be avoided in most cases.

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 :)

Also, there is one more article to finish this series where we will see how to use refs in functional components to have something like instance variables. If you want to check that out :)


# 3. 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://reactjs.org/docs/forwarding-refs.html
https://reactjs.org/docs/hooks-reference.html#useimperativehandle
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.carlrippon.com/react-forwardref-typescript/
https://stackoverflow.com/questions/57005663/when-to-use-useimperativehandle-uselayouteffect-and-usedebugvalue
https://stackoverflow.com/questions/62040069/react-useimperativehandle-how-to-expose-dom-and-inner-methods