Skip to content

2.0.0-beta.15: reconcile() never notifies symbol-keyed nodes — the #2769 fix covers the set trap but not reconcile's diff loops #2851

Description

@yumemi-thomas

Describe the bug

reconcile() never notifies nodes for symbol-keyed store properties:

  1. An effect (or rendered binding) tracking state[SYM] never re-runs when reconcile() changes that property.
  2. Because the stale node then exists, even plain untracked reads of state[SYM] keep returning the old value — the new value written by reconcile is unreachable. (Without any prior tracked read there is no node, and reads fall through to the swapped value — which is why the bug hides in non-rendering tests.)

This is a remaining variant of #2769. The committed fix (bc92d00) covered symbol keys in the set trap (array metadata writes), storeSetter, storePath, and merge/omit enumeration — all via the new ownEnumerableKeys helper. reconcile()'s diff loops still enumerate with Object.keys, which drops symbol keys, so they were not reached by that fix. String-keyed properties update fine through the identical code path (verified as a control).

Your Example Website or App

https://stackblitz.com/edit/solidjs-templates-krnhumcg?file=src%2FApp.tsx

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

const META = Symbol("meta");

export default function App() {
  const [store, setStore] = createStore<any>({ id: 1, label: "old", [META]: "old" });

  return (
    <main style={{ "font-family": "system-ui", padding: "16px" }}>
      <h2>reconcile() skips symbol-keyed bindings</h2>
      <p>
        string key <code>label</code>: <b>{store.label}</b> (control)
      </p>
      <p>
        symbol key <code>[META]</code>: <b>{store[META]}</b>
      </p>
      <button onClick={() => setStore(reconcile({ id: 1, label: "new", [META]: "new" }, "id"))}>
        reconcile fresh data
      </button>
    </main>
  );
}

Steps to Reproduce the Bug or Issue

  1. Initial render shows both rows at old.
  2. Click reconcile fresh data — one reconcile() carrying the new value for both keys. Actual — the string row updates, the symbol row does not:
string key label: new     (control)
symbol key [META]: old

The symbol binding never re-ran, and even a plain store[META] read still returns "old" — the value reconcile wrote is unreachable behind the stale node. The string row, updated by the same call, is the built-in control: the only difference is the key type.

Expected behavior

Symbol-keyed properties reconcile like string-keyed ones — both rows update:

string key label: new     (control)
symbol key [META]: new

Screenshots or Videos

No response

Platform

  • OS: macOS
  • Browser: Chrome
  • Version: 2.0.0-beta.15 (re-verified at next @ e2ebc11c)

Additional context

Root cause: every key enumeration in reconcile's object diff drops symbols (packages/solid-signals/src/store/reconcile.ts):

// reconcile.ts:28-32 — used when the store's $TRACK node is tracked
function getAllKeys(value, override, next) {
  const keys = getKeys(value, override) as string[];   // getKeys → Object.keys
  const nextKeys = Object.keys(next);
  return Array.from(new Set([...keys, ...nextKeys]));
}

// reconcile.ts:187 (applyStateFast) / :337 (applyStateSlow) — untracked path
const keys = tracked ? getAllKeys(previous, undefined, next) : Object.keys(nodes);

Symbol-keyed nodes in the nodes record are invisible to Object.keys(nodes), and symbol keys of next are invisible to getAllKeys, so setSignal is never called for them; the STORE_HAS loops (reconcile.ts:209/:359) have the same gap. Meanwhile applyState* has already swapped STORE_VALUE to next, so the stale node shadows the new value for direct reads too.

Does this exist in Solid 1.x?

Exists in 1.x too — in a worse form. Verified against solid-js 1.9.14: 1.x reconcile doesn't even merge symbol-keyed values (state[sym] stays at the old value after setState(reconcile({ [sym]: 2, a: 2 })); string keys update fine), so no notification question even arises. 2.0 half-fixed this — the value now merges — but subscribers of the symbol-keyed node are still never notified. Not a regression, but 2.0 already handles symbols in its other store paths (getAllKeys, set trap after #2769), so finishing the job in reconcile's diff loops is consistent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions