How to Test useImperativeHandle in React

This article explains how to test components that use React’s useImperativeHandle hook. You will learn how to set up a test environment, create a wrapper component to capture the forwarded reference, and write assertions using React Testing Library and Jest or Vitest to verify that your imperative methods function correctly.

The useImperativeHandle hook is used in conjunction with forwardRef to customize the instance value that a child component exposes to its parent. Because this hook exposes imperative methods rather than standard declarative props, testing it requires a specific approach: you must render a parent wrapper component in your test to capture the ref, call the exposed methods directly on that ref, and assert the resulting changes in the DOM or component state.

Step 1: The Component Under Test

Consider a custom input component that exposes a focus and clear method to its parent using useImperativeHandle.

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

const CustomInput = forwardRef((props, ref) => {
  const inputRef = useRef();
  const [value, setValue] = useState('');

  useImperativeHandle(ref, () => ({
    focusInput() {
      inputRef.current.focus();
    },
    clearInput() {
      setValue('');
    }
  }));

  return (
    <input
      ref={inputRef}
      value={value}
      onChange={(e) => setValue(e.target.value)}
      placeholder="Type here..."
    />
  );
});

export default CustomInput;

Step 2: Writing the Test

To test this component, create a helper test component that holds a ref, passes it to CustomInput, and provides buttons to trigger the imperative methods. This simulates how a real parent component interacts with the ref.

import React, { useRef } from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import CustomInput from './CustomInput';

// Test wrapper component
function TestWrapper() {
  const inputRef = useRef();

  return (
    <div>
      <CustomInput ref={inputRef} />
      <button onClick={() => inputRef.current.focusInput()}>
        Trigger Focus
      </button>
      <button onClick={() => inputRef.current.clearInput()}>
        Trigger Clear
      </button>
    </div>
  );
}

describe('CustomInput with useImperativeHandle', () => {
  test('should focus the input via the exposed focusInput method', async () => {
    render(<TestWrapper />);
    const input = screen.getByPlaceholderText('Type here...');
    const focusButton = screen.getByText('Trigger Focus');

    // Confirm input is not focused initially
    expect(input).not.toHaveFocus();

    // Click the button that calls the imperative method
    await userEvent.click(focusButton);

    // Assert that the input is now focused
    expect(input).toHaveFocus();
  });

  test('should clear the input via the exposed clearInput method', async () => {
    render(<TestWrapper />);
    const input = screen.getByPlaceholderText('Type here...');
    const clearButton = screen.getByText('Trigger Clear');

    // Type text into the input
    await userEvent.type(input, 'Hello World');
    expect(input).toHaveValue('Hello World');

    // Click the button that calls the imperative clear method
    await userEvent.click(clearButton);

    // Assert that the input is cleared
    expect(input).toHaveValue('');
  });
});

Alternative: Calling Methods Directly on the Ref

If you prefer not to add interactive buttons to your test wrapper, you can store the ref in a local variable during rendering and invoke the methods directly inside your test block.

test('should allow direct method calls on the ref', async () => {
  const ref = React.createRef();
  render(<CustomInput ref={ref} />);

  const input = screen.getByPlaceholderText('Type here...');

  // Type some text to test the clear method
  await userEvent.type(input, 'Test Direct');
  expect(input).toHaveValue('Test Direct');

  // Invoke the exposed method directly from the ref object
  ref.current.clearInput();

  expect(input).toHaveValue('');
});

By rendering the ref inside a controlled environment, you can safely trigger imperative handle actions and assert their outcomes using standard React Testing Library queries.