139

I cannot understand why the following useImperativeHandle, useLayoutEffect, and useDebugValue hooks are needed, can you give examples when they can be used, but not examples from the documentation please.

poPaTheGuru
  • 1,009
  • 1
  • 13
  • 35

5 Answers5

233

Allow me to preface this answer by stating that all of these hooks are very rarely used. 99% of the time, you won't need these. They are only meant to cover some rare corner-case scenarios.


useImperativeHandle

Usually when you use useRef you are given the instance value of the component the ref is attached to. This allows you to interact with the DOM element directly.

useImperativeHandle is very similar, but it lets you do two things:

  1. It gives you control over the value that is returned. Instead of returning the instance element, you explicitly state what the return value will be (see snippet below).
  2. It allows you to replace native functions (such as blur, focus, etc) with functions of your own, thus allowing side-effects to the normal behavior, or a different behavior altogether. Though, you can call the function whatever you like.

There could be many reasons you want might to do either of the above; you might not want to expose native properties to the parent or maybe you want to change the behavior of a native function. There could be many reasons. However, useImperativeHandle is rarely used.

useImperativeHandle customizes the instance value that is exposed to parent components when using ref

Example

In this example, the value we'll get from the ref will only contain the function blur which we declared in our useImperativeHandle. It will not contain any other properties (I am logging the value to demonstrate this). The function itself is also "customized" to behave differently than what you'd normally expect. Here, it sets document.title and blurs the input when blur is invoked.

const MyInput = React.forwardRef((props, ref) => {
  const [val, setVal] = React.useState('');
  const inputRef = React.useRef();

  React.useImperativeHandle(ref, () => ({
    blur: () => {
      document.title = val;
      inputRef.current.blur();
    }
  }));

  return (
    <input
      ref={inputRef}
      val={val}
      onChange={e => setVal(e.target.value)}
      {...props}
    />
  );
});

const App = () => {
  const ref = React.useRef(null);
  const onBlur = () => {
    console.log(ref.current); // Only contains one property!
    ref.current.blur();
  };

  return <MyInput ref={ref} onBlur={onBlur} />;
};

ReactDOM.render(<App />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>

useLayoutEffect

While similar to some extent to useEffect(), it differs in that it will run after React has committed updates to the DOM. Used in rare cases when you need to calculate the distance between elements after an update or do other post-update calculations / side-effects.

The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.

Example

Suppose you have an absolutely positioned element whose height might vary and you want to position another div beneath it. You could use getBoundingClientRect() to calculate the parent's height and top properties and then just apply those to the top property of the child.

Here you would want to use useLayoutEffect rather than useEffect. See why in the examples below:

With useEffect: (notice the jumpy behavior)

const Message = ({boxRef, children}) => {
  const msgRef = React.useRef(null);
  React.useEffect(() => {
    const rect = boxRef.current.getBoundingClientRect();
    msgRef.current.style.top = `${rect.height + rect.top}px`;
  }, []);

  return <span ref={msgRef} className="msg">{children}</span>;
};

const App = () => {
  const [show, setShow] = React.useState(false);
  const boxRef = React.useRef(null);

  return (
    <div>
      <div ref={boxRef} className="box" onClick={() => setShow(prev => !prev)}>Click me</div>
      {show && <Message boxRef={boxRef}>Foo bar baz</Message>}
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));
.box {
  position: absolute;
  width: 100px;
  height: 100px;
  background: green;
  color: white;
}

.msg {
  position: relative;
  border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>

With useLayoutEffect:

const Message = ({boxRef, children}) => {
  const msgRef = React.useRef(null);
  React.useLayoutEffect(() => {
    const rect = boxRef.current.getBoundingClientRect();
    msgRef.current.style.top = `${rect.height + rect.top}px`;
  }, []);

  return <span ref={msgRef} className="msg">{children}</span>;
};

const App = () => {
  const [show, setShow] = React.useState(false);
  const boxRef = React.useRef(null);

  return (
    <div>
      <div ref={boxRef} className="box" onClick={() => setShow(prev => !prev)}>Click me</div>
      {show && <Message boxRef={boxRef}>Foo bar baz</Message>}
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));
.box {
  position: absolute;
  width: 100px;
  height: 100px;
  background: green;
  color: white;
}

.msg {
  position: relative;
  border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>

useDebugValue

Sometimes you might want to debug certain values or properties, but doing so might require expensive operations which might impact performance.

useDebugValue is only called when the React DevTools are open and the related hook is inspected, preventing any impact on performance.

useDebugValue can be used to display a label for custom hooks in React DevTools.

I have personally never used this hook though. Maybe someone in the comments can give some insight with a good example.

bobbyz
  • 4,946
  • 3
  • 31
  • 42
Chris
  • 57,622
  • 19
  • 111
  • 137
  • just a question, if i design a reusable component, it contains some child component, and i want to expose a API that can interactive the child component : e.g search function in a grid component and highlight the search result. is it a good reason to use useImperativeHandle – user192344 May 07 '20 at 13:55
  • 1
    @user192344 not enough context for me to answer. It's not completely clear what you need. I suggest you post a new question and explain your problem :) – Chris May 07 '20 at 17:43
  • 1
    @user192344 I think what you are describing is more of a Redux type job, where you would pass the dispatch function returned from useReducer( ). When you want to expose a set of functions to manipulate state (think of it like a state API), while useImperativeHandle( ) could work, it's probably not a good practice to use it like that. – Brain2000 May 27 '20 at 05:48
  • @user192344 If I understand right, this is what you are trying to achieve - You have a `Search` component which is reusable and also there is a child element (say `input`) in it. The reference to that `input` element is used with in the component and you also want to expose this reference out side so that the end user can also access the `input` element instance. If this is the case, then I believe you can use `useImperativeHandle` – singularity Dec 05 '22 at 04:41
20

useImperativeHandle

useImperativeHandle allows you to determine which properties will be exposed on a ref. In the example below, we have a button component, and we'd like to expose the someExposedProperty property on that ref:

[index.tsx]

import React, { useRef } from "react";
import { render } from "react-dom";
import Button from "./Button";

import "./styles.css";

function App() {
  const buttonRef = useRef(null);

  const handleClick = () => {
    console.log(Object.keys(buttonRef.current)); // ['someExposedProperty']
    console.log("click in index.tsx");
    buttonRef.current.someExposedProperty();
  };

  return (
    <div>
      <Button onClick={handleClick} ref={buttonRef} />
    </div>
  );
}

const rootElement = document.getElementById("root");
render(<App />, rootElement);

[Button.tsx]

import React, { useRef, useImperativeHandle, forwardRef } from "react";

function Button(props, ref) {
  const buttonRef = useRef();
  useImperativeHandle(ref, () => ({
    someExposedProperty: () => {
      console.log(`we're inside the exposed property function!`);
    }
  }));
  return (
    <button ref={buttonRef} {...props}>
      Button
    </button>
  );
}

export default forwardRef(Button);

Available here.

useLayoutEffect

This is the same as useEffect, but only fires once all DOM mutations are completed. This article From Kent C. Dodds explains the difference as well as anyone, regarding these two, he says:

99% of the time [useEffect] is what you want to use.

I haven't seen any examples which illustrate this particularly well, and I'm not sure I'd be able to create anything either. It's probably best to say that you ought to only use useLayoutEffect when useEffect has issues.

useDebugValue

I feel like the docs do a pretty good example of explaining this one. If you have a custom hook, and you'd like to label it within React DevTools, then this is what you use.

If you have any specific issues with this then it'd probably be best to either comment or ask another question, because I feel like anything people put here will just be reiterating the docs, at least until we reach a more specific problem.

OliverRadini
  • 6,238
  • 1
  • 21
  • 46
  • 3
    Your example of `useImperativeHandle` doesn't work. – Kevin Ghadyani Jul 23 '19 at 01:05
  • 1
    @sawtaytoes when I built it on code sandbox it did work, the accepted answer seems far better received than this one in any case – OliverRadini Jul 23 '19 at 05:50
  • 1
    `useLayoutEffect` when you need to mutate(change) the DOM generated by React, useful for integrating libraries that mutate the DOM with React. – Victor Castro Nov 10 '19 at 16:21
  • 2
    The `useImperativeHandle` example it's just missing the correct code in `Table.jsx`, I just copied the snippet you provided and it works. Thanks! – lewislbr Mar 23 '20 at 07:39
  • it's very bad example for imperative handle, because you can just pass ref without it to component, and nothing changing – AuthorProxy Jun 11 '20 at 07:54
  • @AuthorProxy I've updated the answer to a version of code which is now working. Hopefully you can reconsider your comment slightly - others have found the previous example to be useful, and I find it helpful to try and keep comments strictly constructive – OliverRadini Jun 11 '20 at 09:49
  • In my case it says: Cannot read properties of null (reading 'someExposedProperty') and I see your code is working on Sandbox – Mahdiyeh Jun 27 '22 at 08:27
13

The useImperativeHandle hook helped me a lot with a use case of mine.

I created a grid component which uses a third-party library component. The library itself has a big data layer with built-in functionality that can be used by accessing the instance of the element.

However, in my own grid component, I want to extend it with methods which perform actions on the grid. Furthermore, I also want to be able to execute those methods from outside of my grid component.

This is easily achievable by adding the methods inside of the useImperativeHandle hook, and then they will be exposed and usable by its parent.

My grid component looks kind of like this:

import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import ThirdPartyGrid from 'some-library';

export default forwardRef((props, forwardedRef) => {
    const gridRef = useRef(null);
    useImperativeHandle(forwardedRef,
        () => ({

            storeExpandedRecords () {
                // Some code
            },

            restoreExpandedRecords () {
                // Some code
            },

        }));
    
    return (
        <div ref={forwardedRef}>
            <ThirdPartyGrid 
                ref={gridRef}
                {...props.config}
            />
        </div>
    );
});

And then in my parent, I can execute those methods:

import React, { useRef, useEffect } from 'react';

export default function Parent () {
    const gridRef = useRef(null);

    const storeRecords = () => {
        gridRef.current.storeExpandedRecords();
    };

    useEffect(() => {
        storeRecords();
    }, []);

    return <GridWrapper ref={gridRef} config={{ something: true }} />
};
Yamo93
  • 514
  • 3
  • 12
3

useImperativeHandle

usually hook expose your functional based component method and properties to other component by putting functional component inside forwardRef example

const Sidebar=forwardRef((props,ref)=>{

       const [visibility,setVisibility]=useState(null)
       
       const opensideBar=()=>{
         setVisibility(!visibility)
       }
        useImperativeHandle(ref,()=>({
            
            opensideBar:()=>{
               set()
            }
        }))
        
       return(
        <Fragment>
         <button onClick={opensideBar}>SHOW_SIDEBAR</button>
         {visibility==true?(
           <aside className="sidebar">
              <ul className="list-group ">
                <li className=" list-group-item">HOME</li>
                <li className=" list-group-item">ABOUT</li>
                <li className=" list-group-item">SERVICES</li>
                <li className=" list-group-item">CONTACT</li>
                <li className=" list-group-item">GALLERY</li>
             </ul>
          </aside>
         ):null}
        </Fragment>
       )
     }
    
    
    //using sidebar component 

 
class Main extends Component{
    
     myRef=createRef();
    
     render(){
      return(
       <Fragment>
         <button onClick={()=>{
                     ///hear we calling sidebar component
                     this.myRef?.current?.opensideBar()
                    }}>
          Show Sidebar
        </button>
        <Sidebar ref={this.myRef}/>
       </Fragment>
      )
     }
 }

Community
  • 1
  • 1
0

I see that there are already answers to this question. So, I just want to share my articles about useImperativeHandle, useEffect, and useLayoutEffect.

I have written an example-based article aboutuseImperativeHandle hook, you can check it out here: useImperativeHandle by Examples

I also wrote an article about effects in React which explains useEffect hook and useLayoutEffect hook differences in detail: A Beginner’s Guide to Effects in React

Mehdi Namvar
  • 1,093
  • 8
  • 8