Skip to content

Commit 35fa5d1

Browse files
committed
feat: use polyfill
1 parent 0b4a67c commit 35fa5d1

2 files changed

Lines changed: 82 additions & 0 deletions

File tree

src/hooks/api-dom/use.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { useContext } from "react";
2+
import { PromiseStatus } from "../../models";
3+
import * as React from "react";
4+
5+
const promiseStatusCache = new WeakMap<Promise<unknown>, PromiseStatus>();
6+
7+
/** Type-guard: distinguishes a React Context object from a Promise. */
8+
function isContext<T>(value: unknown): value is React.Context<T> {
9+
return (
10+
value !== null &&
11+
typeof value === "object" &&
12+
"$$typeof" in (value as object) &&
13+
typeof (value as { Provider?: unknown }).Provider !== "undefined" &&
14+
typeof (value as { Consumer?: unknown }).Consumer !== "undefined"
15+
);
16+
}
17+
18+
/**
19+
* Instruments a promise so its status is always readable synchronously,
20+
* mirroring what React 19's native `use` receives from the scheduler.
21+
*/
22+
function trackPromise<T>(promise: Promise<T>): PromiseStatus {
23+
if (promiseStatusCache.has(promise)) {
24+
return promiseStatusCache.get(promise)!;
25+
}
26+
const entry: PromiseStatus = {
27+
status: "pending",
28+
promise: promise.then(
29+
(value) => {
30+
(entry as unknown as { status: string; value: unknown }).status = "fulfilled";
31+
(entry as unknown as { value: unknown }).value = value;
32+
},
33+
(reason) => {
34+
(entry as unknown as { status: string; reason: unknown }).status = "rejected";
35+
(entry as unknown as { reason: unknown }).reason = reason;
36+
}
37+
),
38+
};
39+
40+
promiseStatusCache.set(promise as Promise<unknown>, entry);
41+
return entry;
42+
}
43+
44+
/**
45+
* **`use`**: Polyfill of React 19's `use` hook for React 16.8 – 18.
46+
*
47+
* Reads the value of a **Promise** or a **React Context**, suspending the
48+
* component (via Suspense) when the promise is still pending, and resuming
49+
* with the resolved value once it settles.
50+
* @see [📖 Documentation](https://react-tools.ndria.dev/hooks/api-dom/use)
51+
*
52+
* @param input - A `Promise<T>` or a `React.Context<T>`.
53+
* @returns The resolved value `T`.
54+
*/
55+
function usePolyfill<T>(input: Promise<T> | React.Context<T>): T {
56+
if (isContext<T>(input)) {
57+
return useContext(input);
58+
}
59+
60+
const tracked = trackPromise(input);
61+
62+
switch (tracked.status) {
63+
case "fulfilled":
64+
return (tracked as { status: "fulfilled"; value: T }).value;
65+
66+
case "rejected":
67+
throw (tracked as { status: "rejected"; reason: unknown }).reason;
68+
69+
case "pending":
70+
default:
71+
throw tracked.promise;
72+
}
73+
}
74+
75+
export const use = ((React as any).use ?? usePolyfill) as typeof usePolyfill;

src/models/use.model.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* @internal
3+
*/
4+
export type PromiseStatus =
5+
| { status: "pending"; promise: Promise<void> }
6+
| { status: "fulfilled"; value: unknown }
7+
| { status: "rejected"; reason: unknown };

0 commit comments

Comments
 (0)