@pylonsync/react 0.4.15 → 0.4.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dynamic.d.ts +10 -2
- package/package.json +3 -3
- package/src/dynamic.test.tsx +21 -0
- package/src/dynamic.tsx +9 -3
package/dist/dynamic.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ComponentType } from "react";
|
|
1
|
+
import React, { type ComponentType } from "react";
|
|
2
2
|
export interface DynamicOptions {
|
|
3
3
|
/** Rendered while the chunk loads (and, when `ssr` is false, on the server). */
|
|
4
4
|
loading?: ComponentType<any> | null;
|
|
@@ -24,6 +24,14 @@ type Loader<P> = () => Promise<{
|
|
|
24
24
|
* The returned component renders `loading` until the chunk arrives, then the
|
|
25
25
|
* real one. Call it at MODULE level, not inside a render — a `dynamic()` call
|
|
26
26
|
* per render creates a new component type each time and remounts the subtree.
|
|
27
|
+
*
|
|
28
|
+
* `ref` reaches the loaded component: React 19 treats it as an ordinary prop,
|
|
29
|
+
* so the spread carries it to a `forwardRef` target's imperative handle. That
|
|
30
|
+
* matters for the exact components people defer — an editor whose save path
|
|
31
|
+
* calls `ref.current.export()` would fail silently otherwise — so it's covered
|
|
32
|
+
* by a test rather than left to React's prop semantics staying put.
|
|
27
33
|
*/
|
|
28
|
-
export declare function dynamic<P extends object>(loader: Loader<P>, options?: DynamicOptions): ComponentType<P
|
|
34
|
+
export declare function dynamic<P extends object>(loader: Loader<P>, options?: DynamicOptions): ComponentType<P & {
|
|
35
|
+
ref?: React.Ref<any>;
|
|
36
|
+
}>;
|
|
29
37
|
export {};
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.4.
|
|
6
|
+
"version": "0.4.17",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"prepack": "bun run build"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@pylonsync/sdk": "0.4.
|
|
18
|
-
"@pylonsync/sync": "0.4.
|
|
17
|
+
"@pylonsync/sdk": "0.4.17",
|
|
18
|
+
"@pylonsync/sync": "0.4.17"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"react": ">=19.0.0"
|
package/src/dynamic.test.tsx
CHANGED
|
@@ -108,3 +108,24 @@ describe("dynamic() with ssr:true", () => {
|
|
|
108
108
|
await waitFor(() => expect(screen.getByTestId("heavy")).toBeDefined());
|
|
109
109
|
});
|
|
110
110
|
});
|
|
111
|
+
|
|
112
|
+
describe("dynamic() and refs", () => {
|
|
113
|
+
test("a ref reaches the loaded component's imperative handle", async () => {
|
|
114
|
+
// React 19 passes `ref` as an ordinary prop, so the spread carries it —
|
|
115
|
+
// but that's incidental enough to be worth pinning: a deferred editor
|
|
116
|
+
// whose save path calls ref.current.export() breaks silently otherwise.
|
|
117
|
+
const Editor = React.forwardRef<{ save: () => string }, { doc: string }>(
|
|
118
|
+
(props, ref) => {
|
|
119
|
+
React.useImperativeHandle(ref, () => ({ save: () => `saved:${props.doc}` }));
|
|
120
|
+
return <div data-testid="ed">{props.doc}</div>;
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
const D = dynamic<{ doc: string }>(async () => ({ default: Editor as any }), {
|
|
124
|
+
loading: Skeleton,
|
|
125
|
+
});
|
|
126
|
+
const ref = React.createRef<{ save: () => string }>();
|
|
127
|
+
render(<D doc="hello" ref={ref} />);
|
|
128
|
+
await waitFor(() => expect(screen.getByTestId("ed")).toBeDefined());
|
|
129
|
+
expect(ref.current?.save()).toBe("saved:hello");
|
|
130
|
+
});
|
|
131
|
+
});
|
package/src/dynamic.tsx
CHANGED
|
@@ -59,11 +59,17 @@ function resolveComponent<P>(
|
|
|
59
59
|
* The returned component renders `loading` until the chunk arrives, then the
|
|
60
60
|
* real one. Call it at MODULE level, not inside a render — a `dynamic()` call
|
|
61
61
|
* per render creates a new component type each time and remounts the subtree.
|
|
62
|
+
*
|
|
63
|
+
* `ref` reaches the loaded component: React 19 treats it as an ordinary prop,
|
|
64
|
+
* so the spread carries it to a `forwardRef` target's imperative handle. That
|
|
65
|
+
* matters for the exact components people defer — an editor whose save path
|
|
66
|
+
* calls `ref.current.export()` would fail silently otherwise — so it's covered
|
|
67
|
+
* by a test rather than left to React's prop semantics staying put.
|
|
62
68
|
*/
|
|
63
69
|
export function dynamic<P extends object>(
|
|
64
70
|
loader: Loader<P>,
|
|
65
71
|
options: DynamicOptions = {},
|
|
66
|
-
): ComponentType<P> {
|
|
72
|
+
): ComponentType<P & { ref?: React.Ref<any> }> {
|
|
67
73
|
const { loading: Loading = null, ssr = false } = options;
|
|
68
74
|
|
|
69
75
|
if (ssr) {
|
|
@@ -81,7 +87,7 @@ export function dynamic<P extends object>(
|
|
|
81
87
|
</Suspense>
|
|
82
88
|
);
|
|
83
89
|
WithSuspense.displayName = "DynamicSSR";
|
|
84
|
-
return WithSuspense as ComponentType<P>;
|
|
90
|
+
return WithSuspense as ComponentType<P & { ref?: React.Ref<any> }>;
|
|
85
91
|
}
|
|
86
92
|
|
|
87
93
|
// Client-only. The fallback renders on the server AND on the first client
|
|
@@ -117,5 +123,5 @@ export function dynamic<P extends object>(
|
|
|
117
123
|
return <Loaded {...(props as any)} />;
|
|
118
124
|
};
|
|
119
125
|
ClientOnly.displayName = "Dynamic";
|
|
120
|
-
return ClientOnly as ComponentType<P>;
|
|
126
|
+
return ClientOnly as ComponentType<P & { ref?: React.Ref<any> }>;
|
|
121
127
|
}
|