Merging refs
Tags: react, webdev, ref, hooks
Published: 2019-10-12 07:19:33

---
title: Merging refs
published: true
description: There are createRef, useRef... but a very important something is missing
cover_image: https://thepracticaldev.s3.amazonaws.com/i/6wsz6td6stt8hp4rq4pm.png
tags: react, webdev, ref, hooks
---
> TL;DR - `useRef` and `createRef` are not the only API calls you might be interested in 👨‍🔬

Let's imagine - you are opening a [React Hooks API Reference](https://reactjs.org/docs/hooks-reference.html#useimperativehandle), looking for the available hooks, and then... your eyes spot a strange one, you probably missed before - `useImperativeHandle`.
You never used it, and probably never needed it, and have no idea what it does. And the provided example is not very helpful to understand the use case. 
So what is it?

```js
function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
```

Well, as I just said - that's not very helpful. I think nobody understands what's written here 🤔. Why it's written here 🤷‍♂️. Let's fix this with a just one line.

```diff
function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
+   // this is the PUBLIC API, we are exposing to a consumer via `ref`
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
```

In other words - once someone would `<FancyInput ref={ref} />` that `ref` would contain __only__ `.focus`.

> Here is a __codesanbox__ you can play with - https://codesandbox.io/s/react-hook-useimperativehandle-64e5j

The power of `useImperativeHandle` is the power of [Bridge](https://en.wikipedia.org/wiki/Bridge_pattern) or, to be more concrete -  a [Facade](https://en.wikipedia.org/wiki/Facade_pattern) pattern (you might know it as a __wrapper__).

However, `useImperativeHandle` is 100% useless, and powerless against the reality - I would prove it below.

Let's decompose everything to the atomic operations, to understand what do we actually need, and what's yet missing.

## 1. I want to do something when Ref changes
Let's imagine you want to expose different states depending on the `ref` value, because _different_ `refs` might mean something _different_.  However, the `wrapper`(and `useImperativeHandle` "exposes" a `wrapper`) you are exposing to the parent is __always static__. That's ok for some cases, but not ok for others. For example, you can't expose a `real ref` - only a `wrapper` around it, always a `wrapper`.

> You need something like `useImperativeHandle(ref, inputRef)` to synchronize values between `ref.current` and `inputRef.current`, but it does not work that wat - only the `useImperativeHandle(ref, () => inputRef.current)` would work, but it will NOT update value when `inputRef` changes. There is no way you can react to `inputRef.current` changes

You might try to use `dependency` list for the `useImperativeHandle` hook to handle this situation. Something like:

```js
useImperativeHandle( 
   ref, 
   () => inputRef.current, 
   [mmm, butWhen, itsGoing, toChange] // 🤷‍♂️ 🤔
);
```
Well, you do not _depend_ on these dependencies(and `eslint` rules will not help you), but something you are attaching `ref` to - is. Even more - you might attach "your" `ref` to the same `forwardProp` component, which might do the same - __you are not controlling what `ref` is__. It's a black box, and there is no way you may safely predict how that blackbox works.

> This pattern is very fragile and please don't ever use it. Keep in mind - the official documentation is asking not to use it as well.

The only way to make this pattern "stable" - is to know when `ref` is updated, and _reactively_ update the `parentRef`. 
How to do it? Use ref `callback`! And that is the official recommendation.

![Hooks API Reference](https://thepracticaldev.s3.amazonaws.com/i/xihl091fqcx5ok39qsqj.png)

And that's also not a great idea. Callbacks are not great, and probably that's why we have got `RefObject`.

How to use `RefObject` and be notified about the change? Easy, and es5 compatible.
```js
function createCallbackRef(onChangeCallback) {
  let current = null;
  return {
    set current(newValue) {
      // new value set
      current=newValue;
      onChangeCallback(newValue);
    }
    get current() {
      // what shall I return?
      return current;
    }
  }
}
```
Object __getters and setters__ are for the rescue. There is another article about this approach, explaining different use cases in depth:

{% link thekashey/the-same-useref-but-it-will-callback-8bo %}

## 2. I want to use Refs to DOM objects
`useImperativeHandle` is good, but it's not helpful when you need to work with real DOM elements.
The problem is still the same - to have __a local ref__, and synchronize it with the parent. You need to kill two refs with one rock. I mean - you need to update two like there is only one!

{% twitter 1174797704423231489 %}

> When developing low level UI components, it is common to have to use a local ref but also support an external one using React.forwardRef. Natively, React does not offer a way to set two refs inside the ref property. This is the goal of this small utility.

By fact - this is very needed and a very powerful feature, as useful as `forwardRef` itself - be able to maintain `ref` locally, still being _transparent_ for the external consumer.

From some point of view, `mergeRef` IS a `forwardRef`, as long as it _forwards_ a given ref, just to more than one location.

## 3. I want to update Refs

However, what is `ref`? It could be an object(`ref object`), it could be a function(`callback ref`). How to set the new value to the provided `ref` if you don't know what it is?

`React` and `useImperativeHandle`, as well as `mergeRef`, are hiding this logic inside, but you might need it in other cases. In `useEffect` for example.
So here you go:
```js
function assignRef<T>(ref, value) {
  if (typeof ref === 'function') {
    ref(value);
  } else if (ref != null) {
    ref.current = value;
  }
  return ref;
}
```

If​ in the future `ref`'s shape would change again, as it did with React 16 release, it would be just one function to update.

## 4. I want to transform my Refs
Which (surprise!) is what `useImperativeHandle` does - it gives you a programmatic way to return something else, derived from the original ref.

So, if you want to:
- have a `local ref`, referring to the real DOM node
- expose ref with `.focus` method to a parent

Then you have to merge and transform
```diff
-useImperativeHandle(ref, () => ({
-  focus: () => {
-    inputRef.current.focus();
-  }
-}));
mergeRefs([innerRef, tranformRef(
  ref, 
  (current) => ({focus:() => current.focus()}
)])
```

How to create `transformRef`? Well, we need some pieces we already had - `callbackRef` and `assignRef`

```js
function transformRef(ref, transformer) {
  return createCallbackRef(
    value => assignRef(ref, transformer(value))
  )
}
```

`transformRef` is a real "bridge" between two worlds, for example in this [issue](https://github.com/theKashey/react-focus-lock/issues/85) someone needed to pass a "ref, stored in a ClassComponent instance, to the parent".

```js
const FocusLock = ({As}) => <As ref={ref} />

const ResizableWithRef = forwardRef((props, ref) =>
  <Resizable {...props} ref={i => i && ref(i.resizable)}/>
  // i is a ref to a Class Component with .resizable property
);
// ...
<FocusLock as={ResizableWithRef}>
```
- `FocusLock` expects `ref` to be a `DOMNode`
- `Resizable` is a __Class__ Component, so we are getting `ref` to a __class instance__
- `i => i && ref(i.resizable)` - a callback ref - _transfers_ `i.resizable` to a focus-lock ref, _adaption_ to the API.

With `transformRef` it would look like

```js
<Resizable {...props} ref={transformRef(ref, i => i.resizable)}/>
```
Looking the same, but does not require `ref` to be a `callback ref` - you don't need to worry about `how` you are going to do something, only about `what` you need to do.

## 5. I want my refs not to remount every time
Not sure you have noticed, but the code above  (yes - all code above) would work, but would not make you (and React) happy.

The problem is: when `ref` is changing, `React` would set `null` to the old one, and then set the right value to the new one. And, as long as all functions above were returning a new function, or a new object every time - every time they would cause an update to the local, and parent refs.
> And if you are using callback refs, and running some effects basing on their values - then it would cause even bigger updates.

Let's don't do that. Let's use `hooks` to memoize `ref`

```js
function useTransformRef(ref, transformer) {
  return useState(() => createCallbackRef(
    value => assignRef(ref, transformer(value))
  ))[0];
}
```

Easy and sound - we are using `useState` with `state fabric` as an argument to create the ref only once, let our code execute more straightforward.

# Conclusion

And there is a library, which already implemented all _atoms_ I've provided above, tested and typed:

{% github theKashey/use-callback-ref %}

- `assignRef`
- `useCallbackRef`/`createCallbackRef`
- `useMergeRefs`/`mergeRefs`
- `useTransformRef`/`transformRef`
- `useRefToCallback`/`refToCallback`

> For the record: All kudos for `mergeRefs` goes to Greg Berge and [react-merge-refs](https://github.com/smooth-code/react-merge-refs)