Skip to content

Commit

Permalink
fix: expose concurrent root in render hook (#1734)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdjastrzebski authored Jan 14, 2025
1 parent 692c55b commit 9efe13e
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 26 deletions.
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

0 comments on commit 9efe13e

Please sign in to comment.