solidjs / solidjs/solid

Shallow computed stores lose leaf identity; hybrid hydration can lose the first answer

Open
#3,498 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
36.1k
Forks
1.1k
Avg merge
9h 18m
Merged PRs (30d)
195

Description

Describe the bug

On Solid 2 next (59c4192a, package version 2.0.0-rc.8), computed stores have two related correctness problems in their draft/snapshot and hydration paths. Both affect ordinary and optimistic stores.

Shallow projections stop being shallow

With { shallow: true }, nested values should remain raw references. However:

  • The client projection draft wraps nested objects in another proxy, so reading a leaf from the draft does not preserve its identity.
  • Loading shadows, SSR snapshots, and hydration replay shadows use JSON.parse(JSON.stringify(...)) even for shallow stores. This copies shared references, turns Dates into strings and NaN into null, drops undefined properties, and cannot represent BigInts or cycles.
  • The SSR draft also deep-proxies shallow leaves. Accessing an object-valued, non-configurable property on a frozen leaf can throw a Proxy invariant error.

These transformations happen inside store handling, before the normal serialization protocol can preserve supported values.

Hybrid store hydration can lose an answer

The hybrid store path flips its hydration gate immediately after construction. That can supersede the server-backed computation before both the first server answer and the DOM claim are complete. With seedLoadingValue: true, a delayed server answer can be lost and the store stays at its seed.

The same path also suppresses the first yield of every client generator run. Only the initial handoff duplicates a server answer; suppressing the first yield again after a dependency change can discard a valid update. Completing hydration can also replace an adopted server error without an explicit refresh.

Reproduction

This synchronous example exposes the shallow draft problem without an application or DOM:

import { createRoot, createStore } from "solid-js";

createRoot(() => {
  const value = Object.freeze({ child: {}, date: new Date(0) });
  createStore(
    draft => {
      console.assert(draft.value === value, "shallow draft changed leaf identity");
      console.assert(draft.value.child === value.child);
    },
    { value },
    { shallow: true }
  );
});

For loading snapshots, use an async compute with { shallow: true, seedLoadingValue: true }, replace the leaf in the draft, then await a deferred promise. After resolution, the replacement should remain the exact object supplied by the compute; it is currently JSON-cloned.

For hybrid hydration:

  1. Server-render an async computed store with seed { count: 0 } and { ssrSource: "hybrid", seedLoadingValue: true }.
  2. Hydrate the loading seed while the serialized first answer is still pending.
  3. Resolve the server answer to { count: 1 }. The store can remain at 0 instead of adopting 1.
  4. With an async generator whose sole yield writes a reactive version(), change that dependency after handoff. The first yield of this new run must be committed rather than discarded as another hydration duplicate.

The proposed fix includes standalone regression tests for object/array roots, ordinary/optimistic stores, shallow loading/SSR snapshots, hybrid loading and subsequent updates, and server-error recovery. Against the unmodified next sources, 18 of those 20 cases fail.

Expected behavior

  • Shallow drafts and snapshots preserve raw leaf references and values. Only the root container is copied where an independent snapshot is required.
  • Hybrid stores adopt the first server answer and finish hydration before starting the live client source.
  • Only the initial client handoff skips its duplicate first yield; subsequent computes commit their first yield normally.
  • An adopted server error remains visible until an explicit refresh, which can obtain a fresh client answer.

Platform

Windows, Node 24.18.0; reproduced with Solid's server and jsdom hydration tests. No browser-specific behavior is required for the shallow-store reproduction.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the computed-store draft/snapshot and hybrid hydration paths in store handling, then run the Solid server and jsdom hydration tests described in the issue. Use the shallow and hybrid reproductions as regression coverage. Done means raw shallow references and values are preserved, the first hybrid answer is adopted, later first yields are committed, and adopted server errors persist until refresh.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.