You are viewing documentation for v14 alpha. This version is under active development and APIs may change.

renderHook function

renderHook

async function renderHook<Result, Props>(
  hookFn: (props: Props) => Result,
  options?: RenderHookOptions<Props>
): Promise<RenderHookResult<Result, Props>>;

Renders a test component that will call the provided callback, including any hooks it calls, every time it renders. Returns a Promise that resolves to a RenderHookResult object, which you can interact with.

This is the recommended default API for testing hooks. It uses async act internally to ensure all pending React updates are executed during rendering, making it compatible with async React features like Suspense boundary or use() hook.

  • Returns a Promise: Should be awaited
  • Async methods: Both rerender and unmount return Promises and should be awaited
  • Suspense support: Compatible with Suspense boundaries and use() hook
import { renderHook, act } from '@testing-library/react-native';
import { useCount } from '../useCount';

it('should increment count', async () => {
  const { result } = await renderHook(() => useCount());

  expect(result.current.count).toBe(0);
  await act(() => {
    // Note that you should wrap the calls to functions your hook returns with `act` if they trigger an update of your hook's state to ensure pending useEffects are run before your next assertion.
    result.current.increment();
  });
  expect(result.current.count).toBe(1);
});
// useCount.js
import { useState } from 'react';

export const useCount = () => {
  const [count, setCount] = useState(0);
  const increment = () => setCount((previousCount) => previousCount + 1);

  return { count, increment };
};

The renderHook function accepts the following arguments:

Callback is a function that is called each render of the test component. This function should call one or more hooks for testing.

The props passed into the callback will be the initialProps provided in the options to renderHook, unless new props are provided by a subsequent rerender call.

options

A RenderHookOptions<Props> object to modify the execution of the callback function, containing the following properties:

initialProps

The initial values to pass as props to the callback function of renderHook. The Props type is determined by the type passed to or inferred by the renderHook call.

wrapper

A React component to wrap the test component in when rendering. This is usually used to add context providers from React.createContext for the hook to access with useContext.

Result

interface RenderHookResult<Result, Props> {
  result: { current: Result };
  rerender: (props: Props) => Promise<void>;
  unmount: () => Promise<void>;
}

The renderHook function returns a Promise that resolves to an object with the following properties:

result

The current value of the result will reflect the latest of whatever is returned from the callback passed to renderHook. The Result type is determined by the type passed to or inferred by the renderHook call.

Note: When using React Suspense, result.current will be null while the hook is suspended.

rerender

An async function to rerender the test component, causing any hooks to be recalculated. If newProps are passed, they will replace the callback function's initialProps for subsequent rerenders. The Props type is determined by the type passed to or inferred by the renderHook call.

Note: This method returns a Promise and should be awaited.

unmount

An async function to unmount the test component. This is commonly used to trigger cleanup effects for useEffect hooks.

Note: This method returns a Promise and should be awaited.

Examples

Here we present some extra examples of using renderHook API.

With initialProps

import { useState, useEffect } from 'react';
import { renderHook, act } from '@testing-library/react-native';

const useCount = (initialCount: number) => {
  const [count, setCount] = useState(initialCount);
  const increment = () => setCount((previousCount) => previousCount + 1);

  useEffect(() => {
    setCount(initialCount);
  }, [initialCount]);

  return { count, increment };
};

it('should increment count', async () => {
  const { result, rerender } = await renderHook((initialCount: number) => useCount(initialCount), {
    initialProps: 1,
  });

  expect(result.current.count).toBe(1);

  await act(() => {
    result.current.increment();
  });

  expect(result.current.count).toBe(2);
  await rerender(5);
  expect(result.current.count).toBe(5);
});

With wrapper

it('should use context value', async () => {
  function Wrapper({ children }: { children: ReactNode }) {
    return <Context.Provider value="provided">{children}</Context.Provider>;
  }

  const { result } = await renderHook(() => useHook(), { wrapper: Wrapper });
  // ...
});

With React Suspense

import { renderHook, act } from '@testing-library/react-native';
import { Text } from 'react-native';

function useSuspendingHook(promise: Promise<string>) {
  return React.use(promise);
}

it('handles hook with suspense', async () => {
  let resolvePromise: (value: string) => void;
  const promise = new Promise<string>((resolve) => {
    resolvePromise = resolve;
  });

  const { result } = await renderHook(useSuspendingHook, {
    initialProps: promise,
    wrapper: ({ children }) => (
      <React.Suspense fallback={<Text>Loading...</Text>}>{children}</React.Suspense>
    ),
  });

  // Initially suspended, result should not be available
  expect(result.current).toBeNull();

  await act(() => resolvePromise('resolved'));
  expect(result.current).toBe('resolved');
});

Need React or React Native expertise you can count on?