Skip to content

fix: expose concurrent root in render hook #1734

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 32 additions & 18 deletions src/render-hook.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
import type { ComponentType } from 'react';
import React from 'react';
import * as React from 'react';
import { renderInternal } from './render';

export type RenderHookResult<Result, Props> = {
rerender: (props: Props) => void;
result: { current: Result };
result: React.MutableRefObject<Result>;
unmount: () => void;
};

export type RenderHookOptions<Props> = {
/**
* The initial props to pass to the hook.
*/
initialProps?: Props;

/**
* Pass a React Component as the wrapper option to have it rendered around the inner element. This is most useful for creating
* reusable custom render functions for common data providers.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
wrapper?: ComponentType<any>;
wrapper?: React.ComponentType<any>;

/**
* Set to `false` to disable concurrent rendering.
* Otherwise `renderHook` will default to concurrent rendering.
*/
concurrentRoot?: boolean;
};

export function renderHook<Result, Props>(
renderCallback: (props: Props) => Result,
hookToRender: (props: Props) => Result,
options?: RenderHookOptions<Props>,
): RenderHookResult<Result, Props> {
const initialProps = options?.initialProps;
const wrapper = options?.wrapper;
const { initialProps, ...renderOptions } = options ?? {};

const result: React.MutableRefObject<Result | null> = React.createRef();

function TestComponent({ renderCallbackProps }: { renderCallbackProps: Props }) {
const renderResult = renderCallback(renderCallbackProps);
function TestComponent({ hookProps }: { hookProps: Props }) {
const renderResult = hookToRender(hookProps);

React.useEffect(() => {
result.current = renderResult;
Expand All @@ -33,18 +45,20 @@ export function renderHook<Result, Props>(
return null;
}

const { rerender: baseRerender, unmount } = renderInternal(
const { rerender: componentRerender, unmount } = renderInternal(
// @ts-expect-error since option can be undefined, initialProps can be undefined when it should'nt
<TestComponent renderCallbackProps={initialProps} />,
{
wrapper,
},
<TestComponent hookProps={initialProps} />,
renderOptions,
);

function rerender(rerenderCallbackProps: Props) {
return baseRerender(<TestComponent renderCallbackProps={rerenderCallbackProps} />);
function rerender(hookProps: Props) {
return componentRerender(<TestComponent hookProps={hookProps} />);
}

// @ts-expect-error result is ill typed because ref is initialized to null
return { result, rerender, unmount };
return {
// Result should already be set after the first render effects are run.
result: result as React.MutableRefObject<Result>,
rerender,
unmount,
};
}
9 changes: 7 additions & 2 deletions website/docs/13.x-next/docs/api/misc/render-hook.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,20 @@ The `props` passed into the callback will be the `initialProps` provided in the

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

### `initialProps`
### `initialProps` {#initial-props}

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`.

## `RenderHookResult`
### `concurrentRoot` {#concurrent-root}

Set to `false` to disable concurrent rendering.
Otherwise, `render` will default to using concurrent rendering used in the React Native New Architecture.

## Result

```ts
interface RenderHookResult<Result, Props> {
Expand Down
12 changes: 6 additions & 6 deletions website/docs/13.x-next/docs/api/render.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,32 @@ test('basic test', () => {

> When using React context providers, like Redux Provider, you'll likely want to wrap rendered component with them. In such cases, it's convenient to create your own custom `render` method. [Follow this great guide on how to set this up](https://testing-library.com/docs/react-testing-library/setup#custom-render).

### Options {#render-options}
## Options

The behavior of the `render` method can be customized by passing various options as a second argument of the `RenderOptions` type:

#### `wrapper` option
### `wrapper` option

```ts
wrapper?: React.ComponentType<any>,
```

This option allows you to wrap the tested component, passed as the first option to the `render()` function, in an additional wrapper component. This is useful for creating reusable custom render functions for common React Context providers.

#### `concurrentRoot` option {#concurrent-root}
### `concurrentRoot` option {#concurrent-root}

Set to `false` to disable concurrent rendering.
Otherwise, `render` will default to using concurrent rendering used in the React Native New Architecture.

#### `createNodeMock` option
### `createNodeMock` option

```ts
createNodeMock?: (element: React.Element) => unknown,
```

This option allows you to pass `createNodeMock` option to `ReactTestRenderer.create()` method in order to allow for custom mock refs. You can learn more about this option from [React Test Renderer documentation](https://reactjs.org/docs/test-renderer.html#ideas).

#### `unstable_validateStringsRenderedWithinText` option
### `unstable_validateStringsRenderedWithinText` option

```ts
unstable_validateStringsRenderedWithinText?: boolean;
Expand All @@ -59,7 +59,7 @@ This **experimental** option allows you to replicate React Native behavior of th

React Test Renderer does not enforce this check; hence, by default, React Native Testing Library also does not check this. That might result in runtime errors when running your code on a device, while the code works without errors in tests.

### Result {#render-result}
## Result

The `render` function returns the same queries and utilities as the [`screen`](docs/api/screen) object. We recommended using the `screen` object as more developer-friendly way.

Expand Down
Loading