React Hooks: useRef, useImperativeHandle, & useLayoutEffect

Looking for a great (remote only) React Dev? Or just feel like having a chat? Visit my profile on LinkedIn and say hi! 😃

React additional hooks

This article continues our discussion of React’s Additional Hooks. The official documentation on how to use these hooks is pretty sparse, so I feel that there’s a need to make a blog post about them. Let’s dive in and get hooked on hooks! (yeah, I know, that was pretty bad… 😂)


The traditional use of useRef is to store a reference to a DOM node. If the DOM node updates, so does the reference. You can access it via the current attribute on the returned variable.

const domRef = useRef()useEffect(() => {
doSomething(domRef.current) // access the DOM node here
}, [])
return <div ref={domRef}><Components /></div>

But did you know that useRef has another fantastic use?

If you have a value that you want to store and you don’t want it to change between renders, then useRef is the hook to use!

A common mistake (that I’ve made myself) is to store and update a value between renders with useState, even though it is not passed down as a prop to any child component. This means that every time you want to update the value with setState, you trigger a series of re-renders that can propagate all the way down the component tree.

If you use useRef instead, there will be no re-render when you update its value. On initialization, useRef returns an object with a current property. You can read from it or set it to a new value.

const ref = useRef({ count: 0 })console.log(ref.current)   // readref.current = ref.current + 1  // update


useImperativeHandle allows you to pass values and functions from a Child component back up to a Parent using a ref. From there, the Parent can either use it itself or pass it to another Child.

Note that you can only pass a ref as a prop to a child that wraps its component in forwardRef.

In the above code, we initialize the ref in the Parent, then pass it as a prop to both Children. Both children wrap their return value in forwardRef so that they can accept a ref as a prop. Notice that the first argument that forwardRef takes is the regular props, and the second argument is the ref.

Now, ChildOne has its own internal state, which is managed with the useState hook, and it has an updateCount method which increments the count.

It then makes the count value available to the Parent by using the useImperativeHandle hook, which takes ref as an argument and returns an object with the count variable.

The Parent can now access count, and passes it as a prop to ChildTwo, which when clicked, logs ref.current.count to the console.

So here you can see a working example of passing values or methods back up the Parent and then down to another Child. Also notice that because we’re using useRef, no re-render occurs in the Parent.


useLayoutEffect fires after the render lifecycle has completed, and just before useEffect executes. It’s syntax is identical to that of useEffect.

const ref = useRef()useEffect(() => console.log(ref.current.getBoundingClientRect()), [])return <button ref={ref}>Press me!</button>

To be more specific — useLayoutEffect is triggered just before the DOM has a chance to paint. It’s a good place to run functionality if you want to do any of the following:

  • Manage focus
  • Manage text selection
  • Trigger animations
  • Integrate with 3rd party DOM libraries

The React docs advise that you first try using useEffect, and if you’re still experiencing UI problems, then reach for useLayoutEffect.

Something to note if you’re using a framework like Next.js: because Next generates HTML on the Server, it will get upset and spit out a warning when it sees useLayoutEffect, since it should really only run on the Client.

Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer’s output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See for common fixes.

There’s a really easy way to fix this — you need to make sure that useLayoutEffect ONLY runs on the Client. Here are a few hacks to achieve this:

if (typeof window !== "undefined")
useLayoutEffect(() => console.log("I only run on the Client!")

Or you can also use:

if (browser.process)
useLayoutEffect(() => console.log("I only run on the Client!")

Happy coding! 😃

I’m a Front End Engineer who loves React, NextJS, and GraphQL. Need a REMOTE React Developer? Contact me at:

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store