Concrete example for React.useImperativeHandle
Tags: react, javascript, hooks
Published: 2020-09-13 15:04:29

A relatively obscure tool in the React hooks toolbox is [useImperativeHandle](https://reactjs.org/docs/hooks-reference.html#useimperativehandle). Despite being around for a quite a while.

Most of the time, it's not needed and even the docs are discouraging its use, opting for more declarative solutions.

{% twitter 1089512992729911300 %}

At times, it can prove useful. In this post I'd like to show one use that we [@Cloudinary](https://cloudinary.com) recently found. 

## Deeper look

First, let's take a closer look at the hook's implementation.
As with other hooks, the actual implementation is published as part of the react-dom package and not in react.

``` javascript

function imperativeHandleEffect(create, ref) {
  if (typeof ref === 'function') {
    ref(create());
  } else if (ref !== null && ref !== undefined) {
    ref.current = create();
  }
}
```
_The code above is a great simplification. The actual code is [here](https://github.com/facebook/react/blob/84558c61ba1e511768e4f775fcf9c7af3a339caf/packages/react-reconciler/src/ReactFiberHooks.new.js#L1278)._

This function is wrapped by a mountEffect() which means it  runs just like useEffect. 

As we can see, useImperativeHandle will run our `create` function and will assign it to the `ref` parameter. If its a function it will be passed as input, otherwise, it will become the .current value.


> useImperativeHandle accepts a dependency list, as with other hooks. However, React will actually add the `ref` to the list of dependencies for you but eslint-plugin-react-hooks doesnt seem to know that.

## Challenge

_So_, what can you do with it beyond the simple example ReactJS provides?
Well, in our case, we're building our own UI Components as part of a design system.

We have a TextField component that we wrapped with a new component: NumberField. For the most part, NumberField is very similar to its Text counterpart. However, we wanted a consistent behavior and look&feel for its up/down buttons. 

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

These however, look different cross browser so we needed our own UI.

Then came the challenging part - how do you control the value of the input from React-land without forcing it into a [controlled component](https://reactjs.org/docs/forms.html#controlled-components)? The use of the component should determine if its controlled or not. So the component itself, shouldn't.

A colleague of mine pointed me to the very useful [HTMLInputElement.stepUp()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/stepUp) and [HTMLInputElement.stepDown()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/stepDown) methods. This meant we can change the input's value without passing `value`. 

Great!

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

But NumberField just wraps TextField. So it needs to be able to use its own ref while passing an outside ref to the inner TextField. 

Another constraint - ref might be a function or it may be an object (result of useRef). So we need to support both (sounds familiar?). 

Here, useImperativeHandle comes to the rescue. It's not like we couldn't solve the issue without it. It just reduced the solution to a very concise, one liner. Whoo!

## Code

First, we define our TextInput. Simplified of course for the purpose of this article.

``` javascript
const TextInput = forwardRef(
    ({ type = "text", defaultValue, value, onChange, className }, ref) => {
      return (
        <input className={className} type={type} ref={ref} value={value} defaultValue={defaultValue} onChange={onChange} />
      );
    }
  );

```


Next, we define a container for our number input that will hide the native up/down buttons.

``` javascript

const NumberInputWrapper = styled.div`
  display: flex;

  input[type="number"] {
    &::-webkit-outer-spin-button,
    &::-webkit-inner-spin-button {
      -webkit-appearance: none;
      margin: 0;
    }

    appearance: textfield;
  }
`;
```

Finally, we define our NumberInput.

``` javascript
const NumberInput = forwardRef((props, ref) => {
  const internalRef = useRef(null);

  useImperativeHandle(ref, () => internalRef.current, []);

  const onUp = useCallback(() => {
    internalRef.current.stepUp();
  }, [internalRef]);

  const onDown = useCallback(() => {
    internalRef.current.stepDown();
  }, [internalRef]);

  return (
    <NumberInputWrapper>
      <TextInput {...props} type="number" ref={internalRef} />
      <NumberButtonsContainer>
        <NumberButton onClick={onUp}>⬆️</NumberButton>
        <NumberButton onClick={onDown}>⬇️</NumberButton>
      </NumberButtonsContainer>
    </NumberInputWrapper>
  );
});
```

The important part in the code above, of course, is the call to useImperativeHandle: 

``` javascript

  useImperativeHandle(ref, () => internalRef.current, []);
```

the first argument is the ref we received from outside. Inside the `create` function, we return the result of our internal ref. This will make it possible for the code outside to use the ref as before. Internally, we can use our internalRef instance to make changes to the input through the DOM. 

Simple!

P.S. Full code example can be found in this [codesandbox](https://codesandbox.io/s/useimperativehandle-example-h8624?file=/src/App.js).