TUTORIAL

React Hooks Part 7: useImperativeHandle and useLayoutEffect

Two escape hatches most tutorials skip: useImperativeHandle for exposing a controlled API from a child component, and useLayoutEffect for DOM measurements that must happen before the browser paints.


SERIES

React Hooks: A Complete Guide

A four-part deep-dive into React Hooks — from managing state with useState to squeezing performance out of useCallback and useMemo. Each article is standalone yet builds on the previous, giving you both quick reference and progressive mastery.

Table of Contents

Two hooks you rarely need — until you do

Most React code never requires useImperativeHandle or useLayoutEffect. But when you're building component libraries, animating DOM nodes, or integrating third-party SDKs, they become essential.


useImperativeHandle — expose a controlled API from a child

By default, a parent can't call methods on a child component. useImperativeHandle changes that by letting a child decide exactly which methods to expose via a ref.

It must be used together with forwardRef.

The pattern

JSX
import { forwardRef, useImperativeHandle, useRef } from 'react'

const FancyInput = forwardRef(function FancyInput(props, ref) {
  const inputRef = useRef(null)

  useImperativeHandle(ref, () => ({
    focus() {
      inputRef.current?.focus()
    },
    clear() {
      if (inputRef.current) inputRef.current.value = ''
    },
  }))

  return <input ref={inputRef} {...props} />
})

The parent gets a ref that only has focus and clear — not the raw DOM node:

JSX
function Form() {
  const inputRef = useRef(null)

  return (
    <>
      <FancyInput ref={inputRef} placeholder="Type here…" />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
      <button onClick={() => inputRef.current.clear()}>Clear</button>
    </>
  )
}

Why not just expose the raw DOM ref?

If you forward the raw DOM ref, you give the parent unrestricted access — they can modify styles, read any property, call any DOM method. useImperativeHandle defines a contract: "here's what you're allowed to do."

This is especially important in design system components like <Modal>, <Tooltip>, or <DatePicker> where you want callers to use open() / close() rather than poking the DOM directly.


useLayoutEffect — synchronous DOM work before paint

useLayoutEffect has the same signature as useEffect, but it fires synchronously after DOM mutations and before the browser paints.

JSX
useLayoutEffect(() => {
  // DOM is updated, browser hasn't painted yet
}, [dependencies])

The difference in timing

Code
useEffect:    render → commit → paint → effect
useLayoutEffect: render → commit → effect → paint

When you need it: measuring the DOM

JSX
import { useState, useLayoutEffect, useRef } from 'react'

function Tooltip({ text, anchorRef }) {
  const tooltipRef = useRef(null)
  const [position, setPosition] = useState({ top: 0, left: 0 })

  useLayoutEffect(() => {
    const anchor  = anchorRef.current.getBoundingClientRect()
    const tooltip = tooltipRef.current.getBoundingClientRect()

    setPosition({
      top:  anchor.bottom + 8,
      left: anchor.left - tooltip.width / 2 + anchor.width / 2,
    })
  }, [text])

  return (
    <div
      ref={tooltipRef}
      style={{ position: 'fixed', top: position.top, left: position.left }}
    >
      {text}
    </div>
  )
}

If you used useEffect instead, the tooltip would briefly flash in the wrong position before jumping to the correct one — because the paint happens between the render and the effect. useLayoutEffect measures and repositions before the user sees anything.

The SSR caveat

useLayoutEffect emits a warning when run on the server (Next.js, Astro SSR) because the DOM doesn't exist during server rendering. The fix:

JSX
// Use useEffect on the server, useLayoutEffect in the browser
import { useEffect, useLayoutEffect } from 'react'
const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect

When to use each

Hook When
useEffect Side effects that don't need to block painting
useLayoutEffect DOM measurements, animations, tooltip positioning
useImperativeHandle Exposing a controlled imperative API from a component

Key takeaways

  • useImperativeHandle + forwardRef define a public API for a component — use it in library components, not in application code.
  • useLayoutEffect fires synchronously before paint — reach for it only when you're reading DOM geometry.
  • For SSR compatibility, alias useLayoutEffect to useEffect on the server.

Was this article helpful?

w

webencher Editorial

Software engineers and technical writers with 10+ years of combined experience in algorithms, systems design, and web development. Every article is reviewed for accuracy, depth, and practical applicability.

More by this author →