-
Notifications
You must be signed in to change notification settings - Fork 0
/
useStaticCompute.tsx
40 lines (34 loc) · 2.13 KB
/
useStaticCompute.tsx
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
import { createContext, PropsWithChildren, useCallback, useContext, useId, useMemo, useRef } from 'react';
export type StaticComputeRunner = (computeId: string, callback: () => Promise<any>) => any;
interface StaticComputeContextValue {
computeRunner: StaticComputeRunner;
getComputeIdFromReactId(reactId: string): string;
}
const StaticComputeContext = createContext<StaticComputeContextValue>({ computeRunner: () => {}, getComputeIdFromReactId: () => '' });
export function StaticComputeProvider({ computeRunner, children }: PropsWithChildren<{ computeRunner: StaticComputeRunner }>) {
const idRef = useRef(0);
const reactIdMapRef = useRef<{ [reactId: string]: number }>({});
// React.useId is stable, but depends on parent component structure.
// Because static computation is gathered from rendering only a subset of the virtual DOM,
// these IDs are not consistent between computation gathering and actual rendering.
// Incrementing IDs do have this consistency, but they encounter issues during hydration,
// where a component might be created twice, thus incrementing the ID multiple times.
// useId does not have that issue since it's based on VDOM structure, so I use
// incrementing IDs for the actual ID and useId to detect if we've seen an element before
// and should reuse its incrementing ID.
const getComputeIdFromReactId = useCallback((reactId: string) => {
if (!(reactId in reactIdMapRef.current)) {
reactIdMapRef.current[reactId] = idRef.current++;
}
return reactIdMapRef.current[reactId].toString();
}, []);
return <StaticComputeContext.Provider value={{ computeRunner, getComputeIdFromReactId }}>
{children}
</StaticComputeContext.Provider>;
}
export function useStaticCompute<T>(callback: () => Promise<T>): T | undefined {
const { computeRunner, getComputeIdFromReactId } = useContext(StaticComputeContext);
const reactId = useId();
const computeId = useMemo(() => getComputeIdFromReactId(reactId), [reactId, getComputeIdFromReactId]);
return useMemo(() => computeRunner(computeId, callback), [computeId, computeRunner, callback]);
}