Skip to main content
useImperativeHandle is a React Hook that lets you customize the handle exposed as a ref.
function useImperativeHandle<T>(
  ref: { current: T | null } | ((inst: T | null) => mixed) | null | void,
  create: () => T,
  deps: Array<mixed> | void | null
): void

Parameters

ref
Ref<T>
required
The ref you received from the forwardRef render function as the second argument.
create
() => T
required
A function that takes no arguments and returns the ref handle you want to expose. The ref handle can be of any type. Usually, you will return an object with the methods you want to expose.
deps
Array<mixed> | void | null
The list of all reactive values referenced inside the create code. React will re-run create and update the ref when dependencies change.
  • If omitted, the handle is recreated on every render
  • If [] (empty array), the handle is created once
  • If [dep1, dep2], the handle updates when dependencies change

Returns

useImperativeHandle returns undefined.

Usage

Exposing a custom ref handle to the parent component

By default, components don’t expose their DOM nodes to parent components. You can opt into this by using forwardRef and useImperativeHandle:
import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const inputRef = useRef(null);
  
  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      }
    };
  }, []);
  
  return <input ref={inputRef} {...props} />;
});

// Usage in parent
function Form() {
  const inputRef = useRef(null);
  
  function handleClick() {
    inputRef.current.focus();
  }
  
  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>Focus input</button>
    </>
  );
}
Do not overuse refs. You should only use refs for imperative behaviors that you can’t express as props: scrolling to a node, focusing a node, triggering an animation, selecting text, and so on.If you can express something as a prop, you should not use a ref. For example, instead of exposing an imperative handle like { open, close } from a Modal component, it’s better to take isOpen as a prop like <Modal isOpen={isOpen} />.

Exposing specific methods

Limit the exposed API to only what the parent needs:
const VideoPlayer = forwardRef(function VideoPlayer({ src }, ref) {
  const videoRef = useRef(null);
  
  useImperativeHandle(ref, () => {
    return {
      play() {
        videoRef.current.play();
      },
      pause() {
        videoRef.current.pause();
      },
      getCurrentTime() {
        return videoRef.current.currentTime;
      }
    };
  }, []);
  
  return <video ref={videoRef} src={src} />;
});

// Parent only has access to the methods you exposed
function App() {
  const playerRef = useRef(null);
  
  function handlePlay() {
    playerRef.current.play();
  }
  
  function handlePause() {
    playerRef.current.pause();
  }
  
  function logTime() {
    console.log(playerRef.current.getCurrentTime());
  }
  
  return (
    <>
      <VideoPlayer ref={playerRef} src="video.mp4" />
      <button onClick={handlePlay}>Play</button>
      <button onClick={handlePause}>Pause</button>
      <button onClick={logTime}>Log time</button>
    </>
  );
}

Common Patterns

Custom input component

const CustomInput = forwardRef(function CustomInput(props, ref) {
  const inputRef = useRef(null);
  
  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      clear() {
        inputRef.current.value = '';
      },
      getValue() {
        return inputRef.current.value;
      },
      setValue(value) {
        inputRef.current.value = value;
      }
    };
  }, []);
  
  return <input ref={inputRef} {...props} />;
});

Form handle

const Form = forwardRef(function Form({ onSubmit }, ref) {
  const [fields, setFields] = useState({});
  
  useImperativeHandle(ref, () => {
    return {
      reset() {
        setFields({});
      },
      setField(name, value) {
        setFields(f => ({ ...f, [name]: value }));
      },
      getFields() {
        return fields;
      },
      submit() {
        onSubmit(fields);
      }
    };
  }, [fields, onSubmit]);
  
  return (
    <form>
      {/* form fields */}
    </form>
  );
});
const Modal = forwardRef(function Modal({ children }, ref) {
  const dialogRef = useRef(null);
  
  useImperativeHandle(ref, () => {
    return {
      open() {
        dialogRef.current.showModal();
      },
      close() {
        dialogRef.current.close();
      }
    };
  }, []);
  
  return (
    <dialog ref={dialogRef}>
      {children}
    </dialog>
  );
});

// Usage
function App() {
  const modalRef = useRef(null);
  
  return (
    <>
      <button onClick={() => modalRef.current.open()}>Open</button>
      <Modal ref={modalRef}>
        <p>Modal content</p>
        <button onClick={() => modalRef.current.close()}>Close</button>
      </Modal>
    </>
  );
}

With dependencies

const Counter = forwardRef(function Counter({ step = 1 }, ref) {
  const [count, setCount] = useState(0);
  
  useImperativeHandle(ref, () => {
    return {
      increment() {
        setCount(c => c + step);
      },
      decrement() {
        setCount(c => c - step);
      },
      reset() {
        setCount(0);
      },
      getValue() {
        return count;
      }
    };
  }, [step]); // Recreate handle when step changes
  
  return <div>Count: {count}</div>;
});

TypeScript

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

// Define the ref handle type
interface InputHandle {
  focus: () => void;
  clear: () => void;
}

interface InputProps {
  placeholder?: string;
}

const MyInput = forwardRef<InputHandle, InputProps>(
  function MyInput(props, ref) {
    const inputRef = useRef<HTMLInputElement>(null);
    
    useImperativeHandle(ref, () => {
      return {
        focus() {
          inputRef.current?.focus();
        },
        clear() {
          if (inputRef.current) {
            inputRef.current.value = '';
          }
        }
      };
    }, []);
    
    return <input ref={inputRef} {...props} />;
  }
);

// Usage with type safety
function Parent() {
  const inputRef = useRef<InputHandle>(null);
  
  function handleClick() {
    inputRef.current?.focus(); // TypeScript knows these methods exist
    inputRef.current?.clear();
  }
  
  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>Focus and clear</button>
    </>
  );
}

Generic component with ref

interface ListHandle<T> {
  scrollToItem: (index: number) => void;
  getItems: () => T[];
}

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

const List = forwardRef(function List<T>(
  props: ListProps<T>,
  ref: React.Ref<ListHandle<T>>
) {
  const listRef = useRef<HTMLDivElement>(null);
  
  useImperativeHandle(ref, () => {
    return {
      scrollToItem(index: number) {
        const element = listRef.current?.children[index] as HTMLElement;
        element?.scrollIntoView();
      },
      getItems() {
        return props.items;
      }
    };
  }, [props.items]);
  
  return (
    <div ref={listRef}>
      {props.items.map(props.renderItem)}
    </div>
  );
}) as <T>(props: ListProps<T> & { ref?: React.Ref<ListHandle<T>> }) => JSX.Element;

Troubleshooting

My ref is null in the parent

Make sure you’re using forwardRef:
// ❌ Without forwardRef - ref won't work
function MyInput(props) {
  return <input {...props} />;
}

// ✅ With forwardRef
const MyInput = forwardRef(function MyInput(props, ref) {
  return <input ref={ref} {...props} />;
});

The handle is recreated on every render

Provide a dependency array:
// ❌ Missing deps array - handle recreated every render
useImperativeHandle(ref, () => {
  return { focus: () => {} };
});

// ✅ With deps array
useImperativeHandle(ref, () => {
  return { focus: () => {} };
}, []);

When should I use useImperativeHandle?

Use useImperativeHandle when:
  • ✅ You need to expose imperative methods (focus, scroll, play/pause)
  • ✅ You want to hide the internal implementation
  • ✅ You need to expose a subset of functionality
Avoid useImperativeHandle when:
  • ❌ You can use props instead
  • ❌ You’re exposing too many methods (component is doing too much)
  • ❌ The parent should control state (use props)
// ❌ Bad: Using ref for state that should be props
const Modal = forwardRef(function Modal({ children }, ref) {
  const [isOpen, setIsOpen] = useState(false);
  
  useImperativeHandle(ref, () => ({
    open: () => setIsOpen(true),
    close: () => setIsOpen(false)
  }));
  
  return isOpen ? <dialog>{children}</dialog> : null;
});

// ✅ Good: Using props for state
function Modal({ isOpen, onClose, children }) {
  return isOpen ? (
    <dialog>
      {children}
      <button onClick={onClose}>Close</button>
    </dialog>
  ) : null;
}

Best Practices

Keep the handle simple

// ✅ Good: Simple, focused API
useImperativeHandle(ref, () => ({
  focus,
  blur,
  scrollIntoView
}), []);

// ❌ Bad: Too many methods, complex API
useImperativeHandle(ref, () => ({
  focus, blur, scrollIntoView, setValue, getValue,
  validate, reset, submit, clear, enable, disable,
  show, hide, highlight, ... // too much!
}), []);

Name methods clearly

// ✅ Clear, imperative names
useImperativeHandle(ref, () => ({
  focus() { /* ... */ },
  reset() { /* ... */ },
  submit() { /* ... */ }
}), []);

// ❌ Confusing names
useImperativeHandle(ref, () => ({
  doIt() { /* ... */ },
  handle() { /* ... */ },
  update() { /* ... */ }
}), []);

Document the ref API

/**
 * Custom input component with imperative methods.
 * 
 * @example
 * const ref = useRef<InputHandle>(null);
 * ref.current?.focus();
 * ref.current?.clear();
 */
interface InputHandle {
  /** Focus the input element */
  focus: () => void;
  /** Clear the input value */
  clear: () => void;
}

const MyInput = forwardRef<InputHandle, InputProps>(
  function MyInput(props, ref) {
    // ...
  }
);