Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/deep-solid2-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"@solid-primitives/deep": major
---

Migrate to Solid.js v2.0 (beta.13)

## Breaking Changes

**Peer dependencies**: `solid-js@^2.0.0-beta.13` and `@solidjs/web@^2.0.0-beta.13` are now required.

### `@solid-primitives/deep`

- `isServer` now imported from `@solidjs/web` (not `solid-js/web`)
- Store imports (`createStore`, `reconcile`, `snapshot`) moved from `solid-js/store` to `solid-js`
- `unwrap` replaced by `snapshot` — returns a plain non-reactive copy of a store
- Store setters now use draft-first form: `setState(s => { s.prop = value; })` replaces path-based setters
- `createEffect` in examples updated to required split compute/apply form
- `trackStore` now correctly handles getters (subscribes to reactive deps accessed through getters) and rejects plain object wrappers around stores (`pojo: false` behavior preserved)
104 changes: 70 additions & 34 deletions packages/deep/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,35 @@ Primitives for tracking and observing nested reactive objects in Solid.
- [`trackStore`](#trackstore) - A more performant alternative to `trackDeep` utilizing specific store implementations.
- [`captureStoreUpdates`](#capturestoreupdates) - A utility function that captures all updates to a store and returns them as an array.

## Comparison with Solid's built-in `deep`

Solid 2.0 ships a `deep` helper in `solid-js` that tracks all nested properties of a store and returns a **plain snapshot** — a non-reactive copy suitable for serialization:

```ts
import { deep } from "solid-js";

createEffect(
() => deep(store),
snapshot => localStorage.setItem("state", JSON.stringify(snapshot))
);
```

This package complements that with three distinct utilities:

| | Solid's `deep` | `trackDeep` | `trackStore` | `captureStoreUpdates` |
|---|---|---|---|---|
| Tracks all nested changes | ✓ | ✓ | ✓ | ✓ |
| Returns live store proxy | — | ✓ | ✓ | — |
| Returns plain snapshot | ✓ | — | — | — |
| Works on plain objects wrapping stores | — | ✓ | — | — |
| Reports what changed and where | — | — | — | ✓ |

**Use Solid's `deep`** when you want to observe all changes and immediately consume a serializable value (e.g. persist to localStorage, send over the wire).

**Use `trackDeep` or `trackStore`** when you need the live reactive proxy back — for example, to pass it reactively to another primitive, or when you want to decide what to do with the store rather than serialize it immediately. `trackStore` is preferred for large or frequently updated stores due to its use of memoized structural subscriptions; `trackDeep` additionally accepts plain objects that contain stores.

**Use `captureStoreUpdates`** when you need to know _what_ changed and _where_ — it returns an array of `{ path, value }` deltas since the last call. Solid's `deep` has no equivalent for this.

## Installation

```bash
Expand Down Expand Up @@ -41,20 +70,23 @@ import { trackDeep } from "@solid-primitives/deep";

const [state, setState] = createStore({ name: "John", age: 42 });

createEffect(() => {
trackDeep(state);
/* execute some logic whenever the state changes */
});
createEffect(
() => trackDeep(state),
() => {
/* execute some logic whenever the state changes */
}
);
```

Or since this has a composable design, you can create _derivative_ functions and use them similar to derivative signals.

```ts
const deeplyTrackedStore = () => trackDeep(sign);
createEffect(() => {
console.log("Store is: ", deeplyTrackedStore());
// ^ this causes a re-execution of the effect on deep changes of properties
});
createEffect(
() => deeplyTrackedStore(),
// ^ this causes a re-execution of the effect on deep changes of properties
value => console.log("Store is:", value)
);
```

`trackDeep` will traverse any "wrappable" object _(objects that solid stores will wrap with proxies)_, even if it's not a solid store.
Expand All @@ -66,15 +98,17 @@ createEffect(() => {
});
```

> **Warning** If you `unwrap` a store, it will no longer be tracked by `trackDeep` nor `trackStore`!
> **Warning** If you `snapshot` a store, it will no longer be tracked by `trackDeep` nor `trackStore`!

```ts
const unwrapped = unwrap(state);
import { snapshot } from "solid-js";

createEffect(() => {
// This will NOT work:
trackDeep(unwrapped);
});
const plain = snapshot(state);

createEffect(
() => trackDeep(plain), // This will NOT work — plain objects are not reactive
() => {}
);
```

## `trackStore`
Expand All @@ -92,10 +126,12 @@ import { trackStore } from "@solid-primitives/deep";

const [state, setState] = createStore({ name: "John", age: 42 });

createEffect(() => {
trackStore(state);
/* execute some logic whenever the state changes */
});
createEffect(
() => trackStore(state),
() => {
/* execute some logic whenever the state changes */
}
);
```

## `captureStoreUpdates`
Expand All @@ -115,7 +151,7 @@ const getDelta = captureStoreUpdates(state);

getDelta(); // [{ path: [], value: { todos: [] } }]

setState("todos", ["foo"]);
setState(s => { s.todos = ["foo"]; });

getDelta(); // [{ path: ["todos"], value: ["foo"] }]
```
Expand All @@ -127,11 +163,13 @@ const [state, setState] = createStore({ todos: [] });

const getDelta = captureStoreUpdates(state);

createEffect(() => {
const delta = getDelta();
/* execute some logic whenever the state changes */
console.log(delta);
});
createEffect(
() => getDelta(),
delta => {
/* execute some logic whenever the state changes */
console.log(delta);
}
);
```

The returned function is not a signal - it won't get updated by itself, it has to be called manually, or under a tracking scope to capture new updates.
Expand All @@ -144,18 +182,16 @@ const [state, setState] = createStore({ todos: [] });
const delta = createMemo(captureStoreUpdates(state));

// both of these effects will receive the same delta
createEffect(() => {
console.log(delta());
});
createEffect(() => {
console.log(delta());
});
createEffect(
() => delta(),
value => console.log(value)
);
createEffect(
() => delta(),
value => console.log(value)
);
```

### Demo

See a demo of this primitive in action [here](https://primitives.solidjs.community/playground/deep).

## Changelog

See [CHANGELOG.md](./CHANGELOG.md)
86 changes: 0 additions & 86 deletions packages/deep/dev/index.tsx

This file was deleted.

7 changes: 4 additions & 3 deletions packages/deep/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
},
"typesVersions": {},
"scripts": {
"dev": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/dev.ts",
"build": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/build.ts",
"vitest": "vitest -c ../../configs/vitest.config.ts",
"test": "pnpm run vitest",
Expand All @@ -62,9 +61,11 @@
"@solid-primitives/memo": "workspace:^"
},
"peerDependencies": {
"solid-js": "^1.6.12"
"@solidjs/web": "^2.0.0-beta.13",
"solid-js": "^2.0.0-beta.13"
},
"devDependencies": {
"solid-js": "^1.9.7"
"@solidjs/web": "2.0.0-beta.13",
"solid-js": "2.0.0-beta.13"
}
}
Loading