-
Notifications
You must be signed in to change notification settings - Fork 278
Expand file tree
/
Copy pathrender-hook.tsx
More file actions
96 lines (80 loc) · 2.82 KB
/
render-hook.tsx
File metadata and controls
96 lines (80 loc) · 2.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import * as React from 'react';
import render from './render';
import renderAsync from './render-async';
import type { RefObject } from './types';
export type RenderHookResult<Result, Props> = {
result: RefObject<Result>;
rerender: (props: Props) => void;
unmount: () => void;
};
export type RenderHookAsyncResult<Result, Props> = {
result: RefObject<Result>;
rerenderAsync: (props: Props) => Promise<void>;
unmountAsync: () => Promise<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?: React.ComponentType<any>;
/**
* Set to `false` to disable concurrent rendering.
* Otherwise `renderHook` will default to concurrent rendering.
*/
concurrentRoot?: boolean;
};
export function renderHook<Result, Props>(
hookToRender: (props: Props) => Result,
options?: RenderHookOptions<Props>,
): RenderHookResult<Result, Props> {
const result = React.createRef<Result>() as RefObject<Result>;
function HookContainer({ hookProps }: { hookProps: Props }) {
const renderResult = hookToRender(hookProps);
React.useEffect(() => {
result.current = renderResult;
});
return null;
}
const { initialProps, ...renderOptions } = options ?? {};
const { rerender: rerenderComponent, unmount } = render(
// @ts-expect-error since option can be undefined, initialProps can be undefined when it should'nt
<HookContainer hookProps={initialProps} />,
renderOptions,
);
return {
result: result,
rerender: (hookProps: Props) => rerenderComponent(<HookContainer hookProps={hookProps} />),
unmount,
};
}
export async function renderHookAsync<Result, Props>(
hookToRender: (props: Props) => Result,
options?: RenderHookOptions<Props>,
): Promise<RenderHookAsyncResult<Result, Props>> {
const result = React.createRef<Result>() as RefObject<Result>;
function TestComponent({ hookProps }: { hookProps: Props }) {
const renderResult = hookToRender(hookProps);
React.useEffect(() => {
result.current = renderResult;
});
return null;
}
const { initialProps, ...renderOptions } = options ?? {};
const { rerenderAsync: rerenderComponentAsync, unmountAsync } = await renderAsync(
// @ts-expect-error since option can be undefined, initialProps can be undefined when it should'nt
<TestComponent hookProps={initialProps} />,
renderOptions,
);
return {
result: result,
rerenderAsync: (hookProps: Props) =>
rerenderComponentAsync(<TestComponent hookProps={hookProps} />),
unmountAsync,
};
}