Skip to content

Commit 4a954e7

Browse files
committed
fix projection SSR promise hydration serialization
Ensure server createProjection promises resolve to the projected state so serialized hydration values are not emitted as undefined on initial load. Made-with: Cursor
1 parent 6692569 commit 4a954e7

3 files changed

Lines changed: 45 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"solid-js": patch
3+
---
4+
5+
Fix server-side `createProjection` promise serialization to resolve with the projected state so hydration receives the correct value.

packages/solid/src/server/signals.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,13 +714,14 @@ export function createProjection<T extends object>(
714714
(promise as any).s = 1;
715715
if (disposed) {
716716
(promise as any).v = state;
717-
return;
717+
return state as T;
718718
}
719719
if (v !== undefined && v !== state) {
720720
Object.assign(state, v);
721721
}
722722
(promise as any).v = state;
723723
markReady();
724+
return state as T;
724725
},
725726
() => {}
726727
);

packages/solid/test/server/ssr-async.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,44 @@ describe("Loading SSR Async", () => {
11511151
expect(fragmentErrors.size).toBe(0);
11521152
});
11531153

1154+
test("Loading + createProjection(async fn, []) serializes resolved projection value for hydration", async () => {
1155+
const { context, serialized, registeredFragments, fragmentResults, fragmentErrors } =
1156+
createMockSSRContext();
1157+
sharedConfig.context = context;
1158+
1159+
async function getTodos() {
1160+
return ["test1", "test2"];
1161+
}
1162+
1163+
createRoot(
1164+
() => {
1165+
Loading({
1166+
fallback: "Loading...",
1167+
get children() {
1168+
const todos = createProjection(() => getTodos(), [] as string[]);
1169+
return ssr(["<pre>", "</pre>"], () => JSON.stringify(todos)) as any;
1170+
}
1171+
});
1172+
},
1173+
{ id: "t" }
1174+
);
1175+
1176+
expect(registeredFragments.size).toBe(1);
1177+
await tick();
1178+
await tick();
1179+
1180+
expect(fragmentResults.size).toBe(1);
1181+
expect([...fragmentResults.values()][0]).toBe('<pre>["test1","test2"]</pre>');
1182+
expect(fragmentErrors.size).toBe(0);
1183+
1184+
const projectionSerialized: any = serialized.get("t0000");
1185+
expect(projectionSerialized).toBeDefined();
1186+
expect(typeof projectionSerialized.then).toBe("function");
1187+
expect(projectionSerialized.s).toBe(1);
1188+
expect(projectionSerialized.v).toEqual(["test1", "test2"]);
1189+
await expect(projectionSerialized).resolves.toEqual(["test1", "test2"]);
1190+
});
1191+
11541192
test("No Errored — sync error during initial render propagates up", () => {
11551193
const { context } = createMockSSRContext();
11561194
sharedConfig.context = context;

0 commit comments

Comments
 (0)