Skip to content

2.0.0-beta.14: reconcile corrupts proxy shape when replacing a tracked array with an object #2774

Description

@yumemi-thomas

Describe the bug

In Solid 2.0.0-beta.14, reconciling a tracked nested store value from an array to a plain object can corrupt the store proxy shape.

After replacing an array value with an object, the value can still behave like an array proxy. Array.isArray(store.value) stays true, and Object.keys(store.value) can throw a proxy invariant error.

Your Example Website or App

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

import { createRenderEffect, createStore, reconcile } from 'solid-js';

export default function App() {
  const [store, setStore] = createStore<any>({ value: [1, 2] });

  createRenderEffect(
    () => store.value,
    () => {}
  );

  return (
    <main>
      <button
        onClick={() => {
          setStore(reconcile({ value: { a: 1 } }, 'id'));

          console.log('isArray:', Array.isArray(store.value));
          console.log('keys:', Object.keys(store.value));
          console.log('value.a:', store.value.a);
        }}
      >
        replace array with object
      </button>
    </main>
  );
}

Steps to Reproduce the Bug or Issue

  1. Open the repro.
  2. Open the browser console.
  3. Click replace array with object.
  4. Observe the console output.
isArray: true
TypeError: 'ownKeys' on proxy: trap result did not include 'length'

Expected behavior

After reconciling { value: [1, 2] } to { value: { a: 1 } }, the nested value should behave like a plain object:

isArray: false
keys: ["a"]
value.a: 1

Screenshots or Videos

No response

Platform

  • OS: macOS
  • Browser: Chrome
  • Version: current stable

Additional context

This appears related to packages/solid-signals/src/store/reconcile.ts. When a nested value has already been tracked and then changes from array to object, reconcile appears to reuse/mutate the existing array-shaped proxy target instead of replacing it with an object-shaped value.

If Array.isArray(previousValue) !== Array.isArray(nextValue), reconcile likely needs to treat the nested value as a replacement rather than a same-shape merge.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    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