React Hooks Explained: useImperativeHandle
Tags: react, typescript, javascript
Published: 2021-04-13 02:57:42

### Table Of Contents
- [A Note From the Author](#01-note-from-author)
- [Intro](#02-intro)
- [Unidirectional Data Flow](#03-unidirectional-data-flow)
- [Adding Imperative Logic](#04-adding-imperative-logic)
- [Imperative Handling and Function Components](#05-imperative-handling-function-components)
- [Use It Wisely](#06-use-it-wisely)
- [Additional Reading](#07-additional-reading)

_____________________

### A Note From the Author <a name="01-note-from-author"></a>
I've seen some decent explanations here and there on how to use React's `useImperativeHandle` hook — Kent C. Dodds' React course has a great short exercise on how to properly use this hook. But I still feel like there's room for more conversation around _exactly when to use_ this hook, because it's the sort of thing you should be doing sparingly and only in certain situations where it's the most logical (or only remaining) choice.

This is my first post here on DEV (✨🥳🎉) and I intend for this to be the first in a series of short articles centered around React and Typescript. I started working with React about four years ago and I'm excited to share with you some of what I've learned since then. If you notice any errors, please do let me know!

_____________________

### Intro <a name="02-intro"></a>
With rare exception, data flow in React apps is unidirectional. Components comprise a hierarchy of parent and child nodes. Child nodes are privy to information and can call functions that have been passed down to them from parent nodes, via a declarative “props” API. Parent nodes, on the other hand, do not have access to (and are not affected by) the internal state of child nodes. Parent nodes also generally do not call functions declared within child components.

Callback functions usually suffice when closer coordination between parent and child nodes is necessary. More intricate situations involving multiple moving parts and dense component hierarchy may call for things like Redux or the built-in Context API. Even so, parent nodes are usually not given direct control over child nodes.

But what about those very rare situations where callbacks, contexts, and whatnot are simply not enough — where the cleanest, most flexible, or perhaps the only option left is to let the parent directly control the child and imperatively dictate what the latter should do? Let’s see how such a situation might arise, and what we can do about it.

_____________________

### Unidirectional Data Flow <a name="03-unidirectional-data-flow"></a>

Let’s say you’ve been tasked with building a “comments feed” component that will be used in a number of different places throughout several applications. The exact use cases will vary; all you have to work with are the following acceptance criteria:

- **Criterion #1**: The comments feed should accept a list of existing comments (an array) as one of its props, and should display them.
- **Criterion #2**: The comments feed should have a form at the bottom which allows the user to add new comments. This form should consist of two fields: one for the user’s name, and one for the new comment itself. At the bottom of the form, there should be a “Submit” button that lets the user request that the new comment be added.
- **Criterion #3**: When the user clicks the button, the comments feed should send up the information from the new comment form (user’s name and new comment) to the parent component that mounted it. It is that parent component’s responsibility to process the request, update the list of existing comments, and give the updated comment list to the comments feed to display.

Here’s what a very basic implementation of the comments feed might look like (we’ll name this component `Comments`):

```typescript
const Comments = (props: {
  comments: [];
  onSubmitComment: (name: string, newComment: string) => void;
}) => {
  // State management for form
  const [values, setValues] = useState({
    name: "",
    newComment: "",
  });
  
  // Handle changes to form fields
  function handleChange (event) {
    setValues((values) => {
      ...values,
      [event.target.name]: event.target.value,
    });
  }

  // Function that renders content of each comment
  function renderComment (comment) { ... }
  
  // Submit comment
  function handleSubmit () {
    const { name, newComment } = values;
    props.onSubmitComment(name, newComment);
  }
  
  return (
    <>
      <ul>
        {props.comments.map(renderComment)}
      </ul>
      
      <h4>Add a comment</h4>
      <form>
        <label for="name">Your Name</label>
        <input
          name="name"
          type="text"
          value={values.name}
          onChange={handleChange}
        />
        
        <label for="newComment">Your Comment</label>
        <textarea
          name="newComment"
          rows={4}
          value={values.newComment}
          onChange={handleChange}
        />
      </form>
      
      <button onClick={handleSubmit}>Submit</button>
    </>
  );
};
```

This component expects to be given two props. The first prop, `comments`, supplies the list of comments to be displayed. The comments are rendered as list-items within an unordered list. This fulfills criterion #1.

The form allows the user to type in their name and the new comment. There’s a “Submit” button at the bottom of the form that can be clicked to submit the new comment. This fulfills criterion #2.

The second prop supplied to this component is a callback function, `onSubmitComment`. This callback function expects two parameters to be passed in: the name of the person submitting the comment, and the comment itself. When the Submit button is clicked, the `handleSubmit` function is executed. Inside it, the `onSubmitComment` callback function is executed and the values that the user typed into the form are passed in. This is how the `Comments` component will “send up” to its immediate parent the new comment that is to be saved. This fulfills the third and final acceptance criterion.

Now let’s see how a “parent” component would implement the `Comments` component:

```typescript
const Article = () => {
  // State management
  const [comments, setComments] = useState([]);
  
  // Load comments when component mounts
  async function loadComments () {
    const existingComments = await fetch(...) // API request to get comments
    setComments(existingComments); // Store comments in state
  }
  useEffect(() => {
    loadComments();
  }, []); 
  
  // Event handlers
  async function addComment (name: string, newComment: string) {
    // API request to persist new comment...
    // Optimistic update of comments list...
    ...
  }
  
  return (
    <div>
      <article>
        ...
      </article>
      ...
      <Comments
        comments={comments}
        onSubmitComment={addComment}
      />
    </div>
  );
};
```

As shown above, the parent component, once mounted, loads the initial set of comments. The comments list, stored in the `comments` state variable, is passed down to the `Comments` component, which is mounted as a child of this parent component. The `addComment()` function is assigned to the `onSubmitComment` prop’s value. When the user clicks the “Submit” button, the `Comments` component is effectively calling the parent component’s `addComment()` function, by way of the `onSubmitComment` prop.

This is a very basic example of coordinating parent and child node behavior without violating unidirectional flow. The values in the new comment form, and the submit button, and any interactions thereof, are none of the parent component’s concern. The parent doesn’t directly “reach in” and grab information stored inside the child component. Instead, the parent component gives the child a callback function and expects the child to call said function whenever a new comment is to be added. The parent cannot call the `handleSubmit()` function declared inside the `Comments` component.

_____________________

### Adding Imperative Logic <a name="04-adding-imperative-logic"></a>

If you’ve worked extensively with forms in React apps, you may be familiar with how `input` elements expose functions like `blur`, `focus`, and `select` which can be used to programmatically blur or focus a field, or to select all text inside a field, respectively. Normally, when the user clicks inside a field, that field is focused, and when the user moves to another field or clicks outside, that previous field is blurred. But sometimes, it’s necessary to do these things without waiting for user input.

When the user first loads a form inside a page or a dialog, it can be beneficial to the user’s experience to immediately place keyboard focus on the first field in the form (or whichever field the user is expected to start typing in first). Doing so saves the user some time and motor [interaction cost](https://www.nngroup.com/articles/interaction-cost-definition/) otherwise needed to move their mouse cursor to the field and click on it.

There are other situations where you may want to do something like this. If the user attempts to submit a form, but there was an error in one of the fields, it would be really nice if the application automatically focused on the field with the error (and made sure that the field in question had been scrolled into view).

Let’s say that we are given an additional acceptance criterion for our new `Comments` component: 

- **Acceptance Criterion 4**: When the comments feed is mounted and made visible to the user, the “Your Name” field should immediately be given keyboard focus.

Revisiting the `Comments` component again, we see that the new comments form currently looks like this:

```typescript
...
  <form>
    <label for="name">Your Name</label>
    <input
      name="name"
      type="text"
      value={values.name}
      onChange={handleChange}
    />
    
    <label for="newComment">Your Comment</label>
    <textarea
      name="newComment"
      rows={4}
      value={values.newComment}
      onChange={handleChange}
    />
  </form>
...
```

We want the first `input`, the “Your Name” field, to be immediately focused as soon as the `Comments` component mounts. It’s not like we can change the input’s value (or some other prop) and expect the input to auto-focus again. The parent (in this case, the `Comments` component) node simply needs a way to directly (_imperatively_) call the focus function on behalf of the child (the `input`).

This is one of the simplest examples of imperative logic in action. We've finally encountered a situation where it's actually called for!

In order to get access to that function, though, we need a way to reference the specific input element in question. In React, we do this by using a _ref_ (we’ll call it `nameInputRef`):

```typescript
const Comments = ...
...
  const nameInputRef = useRef();
  ...
  return (
    ...
      <form>
        <label for="name">Your Name</label>
        <input
          name="name"
          type="text"
          value={values.name}
          onChange={handleChange}
          ref={nameInputRef}
        />
        ...
      </form>
    ...
  );
};
```

The `focus()` function can now be accessed via `nameInputRef.current`. With the help of a `useEffect` hook, we can call this function after the `Comments` component is first mounted and rendered.

```typescript
...
  const nameInputRef = useRef();
  useEffect(() => {
    if (nameInputRef.current) {
      nameInputRef.current.focus();
    }
  }, []);
...
```

_____________________

### Imperative Handling and Function Components <a name="05-imperative-handling-function-components"></a>

Let’s say our `Comments` component is now being used in numerous applications. On some pages, it’s at the bottom. On other pages, it’s placed off to the side. It’s also inside a few dialogs and tooltips. In all these cases, it is immediately rendered with the “Your Name” field auto-focused. However, as its usage increases, developers start to find themselves in situations where the “auto-focus first field on initial mount” behavior is insufficient. 

One day, a developer is tasked with implementing your comments feed in a slightly different manner. At the bottom of the page, there’s a set of collapsible accordion tabs, each with different content within. One of these accordion tabs contains the comments feed. To view the comments feed, the user must expand the accordion tab by clicking “View Comments”, like so:

![Comments section, collapsed vs expanded](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y90n3dyuak57q2cz3x80.png)

The developer working on this was told that whenever the comments section is expanded, the “Your Name” field must always be initially auto-focused. They achieved this by mounting the comments feed only when the accordion tab is expanded, and unmounting it when it is collapsed. This way, expanding the accordion tab always results in the comments feed being freshly re-mounted. Whenever this happens, the `useEffect` side effect is executed, and the “Your Name” field is once again auto-focused.

The project manager and UX lead, however, were not satisfied with this workaround. You see, if a user begins typing a comment and then collapses the comments section, whatever they painstakingly typed in will be instantly annihilated when the comments feed is unmounted. After expanding the comments section again, they will find to their dismay that everything they wrote is now lost to the sands of time.

There are some other ways of getting around this issue: you could temporarily store (in local storage, for instance) whatever the user typed in. These stored values could then be passed in to the comments feed as “initial values” when the component is re-mounted.

But for the sake of our discussion, what if we could avoid adding more props and making significant changes to the `Comments` component by doing something similar to what we did earlier with the `input` field? What if the `Comments` component contained a function to focus on the “Your Name” field, and exposed this function to any parent implementing it, just like the `focus()` function exposed by the `input` element? This function could then be imperatively called by any parent, whenever necessary.

#### Step 1: Define a function in the child component
Let’s first define said function inside the `Comments` component. We’ll call it `focusOnForm()`:

```typescript
const Comments = ...
...
  const nameInputRef = useRef();

  function focusOnForm () {
    if (nameInputRef.current) {
      nameInputRef.current.focus();
    }
  }
  useEffect(focusOnForm, []);
...
```
All we’ve really done so far is move all the logic previously defined inside the `useEffect` hook to its own separate function. We are now calling that function inside the `useEffect`.

Remember how we needed to reference the specific `input` element by way of a `ref` in order to access its `focus()` function? We’ll need to do something similar in order to allow the parent component to access the `focusOnForm()` function inside the `Comments` component.

#### Step 2: Define a ref in the parent component and pass it to the child
Let’s go back up to the parent now. First, we’ll define a new ref, called `commentsFeedRef`. Then, we’ll assign the ref to the `Comments` component, via the `ref` prop, just as we did with the `input` element:

```typescript
const Article = () => {
  ...
  const commentsFeedRef = useRef();
  ...
  return (
    ...
    <Comments
      comments={comments}
      onSubmitComment={addComment}
      ref={commentsFeedRef}
    />
  );
};
```

If this was 2018, and our `Comments` component was a class component, this would be perfectly fine and we would be well on our way. But this is the f u t u r e, man — the `Comments` component is a function component. And unlike class components, function components do not have an associated component instance when they are mounted. In other words, there is no way to access some “instance” of a function component via a default `ref` property. There’s a little more work we must do first.

Simply adding a `ref` property to the existing props on the Comments component will not work either, by the way, so the following approach is also incorrect:

```typescript
const Comments = (props: {
  comments: [];
  onSubmitComment: (name: string, newComment: string) => void;
  ref,
}) => ...
```
Instead, we have to use the `forwardRef` feature provided by React in order to pass a ref to our function component.

#### Step 3: Use forwardRef to allow a ref to be passed to the child

There are a few different ways of doing this but here’s the approach I usually prefer, as it’s pretty clean and easy to follow. We first need to define the component as a named function instead of an anonymous function assigned to a constant:

```typescript
function Comments (
  props: {
    comments: [];
    onSubmitComment: (name: string, newComment: string) => void;
  }
) {
  ...
  function focusOnForm () { ... }
  ...
}
```

Let’s say we were previously exporting this component as a module-level default export:

```typescript
export default Comments;
```

We now need to first pass the `Comments` component to the `forwardRef` higher-order component, and then export the result:

```typescript
export default React.forwardRef(Comments);
```

Next, we’ll add the `ref` property to the `Comments` component. Notice, however, that the `ref` property is kept separate from the main component props:
```typescript
function Comments (
  props: {
    comments: [];
    onSubmitComment: (name: string, newComment: string) => void;
  },
  ref
) {
  ...
  function focusOnForm () { ... }
  ...
}
```

The parent component can now pass a ref to the `Comments` component, and use it to call the `focusOnForm()` function. When we call it, we’ll probably do something like this:
```typescript
...
commentsFeedRef.current.focusOnForm();
...
```

But this still won't work. What gives?

Well, the ref's `current` property doesn't actually have the `focusOnForm` function in it yet. We first need to define exactly what gets exposed via the `current` property.

#### Step 4: Expose function(s) via passed ref, with useImperativeHandle

We’ll accomplish that with `useImperativeHandle`:
```typescript
function Comments (
  props: {
    comments: [];
    onSubmitComment: (name: string, newComment: string) => void;
  },
  ref
) {
  ...
  function focusOnForm () { ... }
  useImperativeHandle(
    // Parameter 1: the ref that is exposed to the parent
    ref,
    // Parameter 2: a function that returns the value of the ref's current property,
    // an object containing the things we're trying to expose (in this case, just
    // one function)
    () => {
      return {
        focusOnForm: focusOnForm,
      }
    }
  );
  ...
}
```

We’re passing two parameters into `useImperativeHandle`. The first parameter simply indicates the ref that is being exposed to the parent.

In the second parameter, we pass a function that returns an object containing the various functions and properties we are trying to expose to the parent. `useImperativeHandle` will return this object when the parent accesses the `current` property of the ref passed in as the first parameter.

We can simplify it, like so:
```typescript
useImperativeHandle(
  ref,
  () => ({
    focusOnForm,
  })
);
```

There’s actually a third, optional parameter. You can pass in an array of dependencies, and `useImperativeHandle` will recalculate what is to be returned when any of those dependencies change. This can be useful if anything you’re returning is being influenced by the child component’s state; for instance:
```typescript
const [someValue, setSomeValue] = useState<number>(...);
...
useImperativeHandle(
  ref,
  () => ({
    someFunction: (value) => value * someValue,
  }),
  [someValue]
);
```
For now, though, we won’t be needing that.

Now, when the `Comments` component is passed a ref, it will immediately assign an object to the value of the ref’s `current` property. For now, this object only contains the `focusOnForm()` function.

#### Step 5: Call function(s) exposed by child, via the ref passed to the child

Going back to the parent component, we can see how the `focusOnForm()` function, defined inside the child component, can now be called inside the parent:

```typescript
const Article = () => {
  ...
  const commentsFeedRef = useRef();
  ...
  function focusOnNewCommentForm () {
    if (commentsFeedRef.current) {
      commentsFeedRef.current.focusOnForm();
    }
  }
  ...
  return (
    ...
    <Comments
      comments={comments}
      onSubmitComment={addComment}
      ref={commentsFeedRef}
    />
  );
};
```

With this, the developer can now easily call `focusOnForm()` whenever necessary, without having to unmount and remount the `Comments` component. The `showComments` variable shown below controls the expanded / collapsed state of the comments section. A `useEffect` hook watches for changes in its value. Whenever its value changes to `true`, we'll call `focusOnForm()`.

```typescript
const Article = () => {
  ...
  const [showComments, setShowComments] = useState(false);
  useEffect(() => {
    if (showComments && commentsFeedRef.current) {
      commentsFeedRef.current.focusOnForm();
    }
  }, [showComments]);
  ...
  return (
    ...
    <Accordion ...>
      <Accordion.Tab show={showComments}>
        <Comments
          comments={comments}
          onSubmitComment={addComment}
          ref={commentsFeedRef}
        />
      </Accordion.Tab />
    </Accordion>
  );
};
```
Great! Now the “Your Name” field in the new comment form will always be re-focused whenever the comments feed is displayed again, even though the `Comments` component has not been unmounted and re-mounted.

_____________________

### Use It Wisely <a name="06-use-it-wisely"></a>

At the end of the day, `useImperativeHandle` isn’t used very often, and with good reason – it’s an escape hatch, a fire escape, a method of absolute last resort when other options have failed or are simply not viable.

One of the rare spots where I’ve encountered `useImperativeHandle` in the wild is when there's some kind of scrollable area and button to let the user scroll all the way back up to the top. It’s simple enough to just get the element in question (either via ref, or with a `document.querySelector` query), and call `scrollTop = 0`. But you don’t want developers to have to write this logic every time they implement the component in question – the component should expose some property that can be passed a value which triggers the effect, right?

But you'll quickly find that passing in a value doesn’t make much sense for an imperative action. What would you pass in? A boolean variable (`onRequestScrollToTop`) with the value `true`? Does this variable then get set back to `false`? Does the parent set it back to `false` with `setTimeout` and a short delay? Or is there a callback function (`onScrollToTop`) which is executed after the scroll-to-top is complete, at which time the variable in question is set to `false`? All of these sound equally awful and unnecessary.

It’s peculiar and rare situations like these where `useImperativeHandle` actually shines and should actually be considered. Conversely, if you don’t find yourself asking these types of questions, you can probably accomplish what you’re trying to do without using `useImperativeHandle`.

Here’s another thing to think about: when you’re creating components for others and publishing them as open-source tools, it’s impossible to predict in advance all the ways in which they are going to be used. There are clear advantages to constructing our components in a way that maximizes their flexibility. That comments feed, for instance: there’s nothing saying it has to be used in an accordion. Perhaps, in some rare cases, adding `useImperativeHandle` could allow developers to use specific features in specific situations without us being forced to drastically alter the original component every single time a new, unique situation arises.


_____________________

### Additional Reading <a name="07-additional-reading"></a>
* [Liz Denhup, "Understanding Unidirectional Data Flow in React"](https://medium.com/@lizdenhup/understanding-unidirectional-data-flow-in-react-3e3524c09d8e)
* [React Unidirectional Data](https://learn.co/lessons/react-unidirectional-data)