Skip to content
Open
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
14 changes: 14 additions & 0 deletions .changeset/keyed-solid2-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@solid-primitives/keyed": 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.

- `isServer` is now imported from `@solidjs/web` (was `solid-js/web`)
- `JSX` type is now imported from `@solidjs/web` (was `solid-js`)
- `Rerun` props: `on` type changed from `AccessorArray<S> | Accessor<S>` to `Accessor<S>[] | Accessor<S>` (`AccessorArray` was removed from `solid-js`)
- Internal signals in `keyArray` now use `ownedWrite: true` to satisfy Solid 2.0's owned-scope write restrictions
12 changes: 6 additions & 6 deletions packages/keyed/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@solid-primitives/keyed",
"version": "1.5.3",
"version": "2.0.0",
"description": "Control Flow primitives and components that require specifying explicit keys to identify or rerender elements.",
"author": "Damian Tarnawski @thetarnav <gthetarnav@gmail.com>",
"license": "MIT",
Expand Down Expand Up @@ -46,18 +46,18 @@
"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",
"vitest": "vitest -c ../../configs/vitest.config.solid2.ts",
"test": "pnpm run vitest",
"test:ssr": "pnpm run vitest --mode ssr"
},
"devDependencies": {
"@solid-primitives/refs": "workspace:^",
"@solid-primitives/utils": "workspace:^",
"solid-js": "^1.9.7",
"solid-transition-group": "^0.2.3"
"@solidjs/web": "2.0.0-beta.13",
"solid-js": "2.0.0-beta.13"
},
"peerDependencies": {
"solid-js": "^1.6.12"
"@solidjs/web": "^2.0.0-beta.13",
"solid-js": "^2.0.0-beta.13"
},
"typesVersions": {}
}
34 changes: 23 additions & 11 deletions packages/keyed/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import {
createMemo,
createRoot,
createSignal,
type JSX,
on,
onCleanup,
type Setter,
untrack,
$TRACK,
mapArray,
type AccessorArray,
} from "solid-js";
import { isServer } from "solid-js/web";
import type { JSX } from "@solidjs/web";
import { isServer } from "@solidjs/web";
import { INTERNAL_OPTIONS } from "@solid-primitives/utils";

const FALLBACK = Symbol("fallback");

Expand Down Expand Up @@ -114,10 +113,10 @@ export function keyArray<T, U, K>(

function addNewItem(list: U[], item: T, i: number, key: K): void {
createRoot(dispose => {
const [getItem, setItem] = createSignal(item);
const [getItem, setItem] = createSignal(item as Exclude<T, Function>, INTERNAL_OPTIONS);
const save = { setItem, dispose } as Save;
if (mapFn.length > 1) {
const [index, setIndex] = createSignal(i);
const [index, setIndex] = createSignal(i, INTERNAL_OPTIONS);
save.setIndex = setIndex;
save.mapped = mapFn(getItem, index);
} else save.mapped = (mapFn as any)(getItem);
Expand Down Expand Up @@ -277,18 +276,31 @@ export type RerunChildren<T> = ((input: T, prevInput: T | undefined) => JSX.Elem
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/refs#Rerun
*/
export function Rerun<S>(props: {
on: AccessorArray<S> | Accessor<S>;
on: Accessor<S>[] | Accessor<S>;
children: RerunChildren<S>;
}): JSX.Element;
export function Rerun<
S extends (object | string | bigint | number | boolean) & { length?: never },
>(props: { on: S; children: RerunChildren<S> }): JSX.Element;
export function Rerun(props: { on: any; children: RerunChildren<any> }): JSX.Element {
const key = typeof props.on === "function" || Array.isArray(props.on) ? props.on : () => props.on;
const key =
typeof props.on === "function" || Array.isArray(props.on) ? props.on : () => props.on;
const getKey = Array.isArray(key)
? () => (key as Accessor<unknown>[]).map(fn => fn())
: (key as Accessor<unknown>);

let prevKey: unknown;
return createMemo(
on(key, (a, b) => {
() => {
const currentKey = getKey();
const child = props.children;
return typeof child === "function" && child.length > 0 ? (child as any)(a, b) : child;
}),
const el =
typeof child === "function" && (child as Function).length > 0
? (child as any)(currentKey, prevKey)
: child;
Comment on lines +294 to +300
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid tracking child-internal dependencies in Rerun memo.

children is executed in a tracked createMemo context, so any signal reads during child evaluation can subscribe Rerun to dependencies unrelated to props.on. That changes rerun semantics versus the old on(...) behavior.

Proposed fix
   return createMemo(
     () => {
       const currentKey = getKey();
       const child = props.children;
-      const el =
-        typeof child === "function" && (child as Function).length > 0
-          ? (child as any)(currentKey, prevKey)
-          : child;
+      const el = untrack(() =>
+        typeof child === "function" && (child as Function).length > 0
+          ? (child as any)(currentKey, prevKey)
+          : child,
+      );
       prevKey = currentKey;
       return el;
     },
     { equals: false },
   ) as unknown as JSX.Element;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/keyed/src/index.ts` around lines 294 - 300, The memo used for Rerun
is inadvertently tracking any signals read when evaluating props.children,
causing Rerun to subscribe to child-internal dependencies; to fix, evaluate the
child without tracking by wrapping the child invocation in an untracked context
(e.g., Solid's untrack) so only props.on (and getKey) control reruns—update the
anonymous memo callback that calls getKey and executes props.children to call
the child inside untrack(() => ...) so child signal reads do not become
dependencies of the Rerun memo.

prevKey = currentKey;
return el;
},
{ equals: false },
) as unknown as JSX.Element;
}
Loading