Solidjs v2 reviewer
Claude Code skills for SolidJS 2.0 — write, migrate, and review solid-js 2.x code without React/1.x reflexes
npx -y skills add khmm12/solidjs-v2-skills --skill solidjs-v2-reviewerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Review SolidJS 2.0 code for React-isms, Solid 1.x-isms, and reactivity bugs. Use when reviewing diffs, PRs, or files in a project that depends on solid-js 2.x / @solidjs/web — including self-review after generating Solid 2.0 code.
SKILL.md
9.6 KB, ~2.5k tokens by cl100k_base, as published. Nobody here has run it
Review Solid 2.0 code
Hunt the two prior-knowledge bug classes — React reflexes and Solid 1.x reflexes — plus 2.0-specific reactivity mistakes. Severity guide: 🔴 broken behavior, 🟡 dev-mode diagnostic / lost reactivity, 🔵 style drift.
Confirm the project is actually v2 first (solid-js major 2 in package.json /
@solidjs/web in deps). Reviewing a 1.x project against this list produces
garbage findings.
Pass 1 — greppable smells
Run these over the changed files; each hit needs a fix or a justification.
Solid 1.x-isms
| Grep | Verdict | Fix |
|---|---|---|
from ['"]solid-js/(web|store|h|html|universal) | 🔴 module not found | @solidjs/web, store APIs from solid-js, @solidjs/h… |
createResource|useTransition|startTransition | 🔴 removed | async memo + <Loading>; built-in transitions / isPending |
\bbatch\s*\( | 🔴 removed | delete wrapper; flush() only for sync read-after-write |
createComputed|createMutable|modifyMutable|createDeferred | 🔴 removed | memo / split effect / createSignal(fn); createStore drafts |
\bon\s*\( as effect dep helper, onMount|onError|catchError | 🔴 removed | split effect compute; onSettled; <Errored> / effect error |
<Suspense|<SuspenseList|<ErrorBoundary|<Index\b | 🔴 removed | <Loading> / <Reveal> / <Errored> / <For keyed={false}> |
mergeProps|splitProps|unwrap\s*\(|createSelector | 🔴 removed | merge / omit / snapshot / createProjection |
\.Provider\b | 🔴 removed | <Ctx value={...}> — context is the provider |
classList= | 🔴 removed | class={{...}} / class={[...]} |
use:[a-zA-Z]|attr:|bool:|on:[a-z]|oncapture: in JSX | 🔴 removed | ref factories; standard attributes; onClick + ref for native opts |
produce\s*\( in setters | 🟡 redundant | drafts are the default |
setStore\s*\(\s*["'] (path-style first arg) | 🔴 wrong API | draft setter or storePath(...) |
/\*@once\*/ | 🟡 ignored marker | reactive read / defaultValue / untrack |
\.loading\b|\.error\b on async values | 🔴 no such props | <Loading>/isPending(() => x()) for loading (bare refresh() is silent — pair with affects() for a loud reload) / <Errored> for error |
React-isms
| Grep / pattern | Verdict | Fix |
|---|---|---|
function \w+\(\s*\{ (destructured props) | 🟡 reactivity dead + warns | props.x access |
useState|useEffect|useMemo|useRef|useCallback | 🔴 wrong framework | Solid primitives |
<X value={count} /> passing an accessor where a value is expected | 🔴 child gets a function | value={count()} — collapse at the JSX boundary |
key= prop on list items | 🟡 no-op | <For keyed={...}> modes |
className|`${...}`/.join(" ") class building | 🔵 reflex | class array/object form |
| deps-array thinking: effect re-created per "render" | 🟡 model error | components run once; compute phase = deps |
2.0-specific
| Pattern | Verdict | Fix |
|---|---|---|
Single-callback createEffect(fn) | 🔴 throws | split (compute, apply) |
createEffect(fn, 0) / createMemo(fn, 0) initial values | 🔴 wrong arg | options object; prev default parameter |
| Setter then immediate read of same signal/DOM | 🔴 stale read | flush() or restructure |
| Signal/store write inside memo/compute/component body | 🔴 throws in dev | derive, or move write to handler/action |
actionFn() invoked inside memo/compute/component body | 🔴 dev error (ACTION_CALLED_IN_OWNED_SCOPE, beta.17); may livelock in prod | invoke from handler/effect callback/onSettled |
ownedWrite: true on app state | 🟡 escape-hatch abuse | derive instead; ownedWrite is for internal flags |
Top-level const x = props.x / store read in component body | 🟡 warns, stale | read in JSX/memo; untrack if deliberate |
onCleanup inside onSettled/createTrackedEffect | 🔴 throws | return cleanup |
Cleanup returned from onSettled fired out of band (event handler/tracked effect/nested onSettled) | 🔴 dev error (beta.16), dropped in prod | call the setup helper from the component body (owned scope) |
Primitives created inside onSettled/tracked effect | 🔴 throws | create in component body |
| Store proxy passed compute→apply, read in apply | 🟡 warns, won't re-run | extract plain values / deep(store) in compute |
Async read with no <Loading> ancestor | 🟡 root mount deferred | add boundary where fallback UI is wanted |
async function* memo over a socket/emitter/observable with no up-front onCleanup | 🔴 leaks on dispose/re-run | onCleanup (before the first await/yield) that cancels the source; try/finally/.return() can't unwind a parked generator |
refresh() called inside a computation | 🔴 throws | call from handlers/actions |
serverFn.GET property access, serverFn.withOptions( on a server function reference | 🔴 removed | GET(fn) wrapper at declaration site; withMeta(fn, meta) for metadata; prepareRequest for session-dynamic transport (see solidjs-v2 skill, references/server-functions.md) |
isRefreshing( call (or imported from solid-js) | 🔴 removed in beta.15 | gone from solid-js exports; detect a refresh re-run by key comparison, or use isPending/<Loading> |
<For> callback shape vs keying mode mismatch (item() on keyed, i() on keyed={false}) | 🔴 type/runtime error | check the mode table |
Dynamic boolean keyed={cond()} with function children | 🟡 ambiguous shape | literal mode or key function |
useX-with-throw context wrapper hooks | 🔵 dead boilerplate | direct useContext (throws by itself) |
camelCase DOM attributes (tabIndex, readOnly) | 🟡 wrong attribute | lowercase; handlers stay camelCase |
merge(..., maybeUndefined) assuming skip semantics | 🔴 silently overrides | filter keys or restructure defaults |
Pass 2 — judgement checks (not greppable)
- Derive vs write-back: any effect whose apply phase sets reactive state is suspect — usually a memo/projection in disguise.
- Boundary ownership:
isPendingreads placed under theLoadingboundary that owns the data read? Pending indicators outside can never fire. - Mutation shape: server writes wrapped in
action()with optimistic state and a finalrefresh()? Ad-hoc async handlers flipping flags are the 1.x smell in new clothes. - Action call site: an action may be defined in a component, but is it invoked only from an imperative scope? A component-body/computation call is a transaction-starting write and throws in dev mode (since beta.17).
- Optimistic spinner off
isPending: a "Saving…" indicator driven byisPendingon data the same action just wrote optimistically can never show — not because the optimistic write masks it (that mask is removed as of beta.21; optimistic writes are verdict-inert), but because a barerefresh()after the write is a silent same-question re-ask and was never going to flipisPending. The flag belongs in the data (co-writtenpending: trueor a separatecreateOptimistic(false)); if the reload itself should read pending, that needs an explicitaffects(target)before therefresh(). - Stale beta.17–20 mask assumptions: code (or comments) that reason about
an optimistic write "masking"
isPendingstore-wide, or that treat a barerefresh()as if it were pending on its own — both were beta.17–beta.20 behavior, removed/superseded in beta.21 (question-scoped-pending-affects). On beta.21+ typings this silently changes UI (a spinner that used to show now doesn't, or vice versa) with no compiler error to catch it — flag anyisPendinguse next to an optimistic write and check it against the current rule, not habit. - Granularity: selection/derived caches notifying whole collections →
createProjection. Fixed-slot lists diffed with<For>→<Repeat>. - Ownership: module-scope effects/roots intentional? Detached lifetime must
be explicit (
runWithOwner(null, ...)). - Composable naming: a
createX/useXprefix should match lifecycle, not React habit —createXmakes a fresh instance owned by the caller,useXis a shared singleton or accesses an already-created thing (useContext).useXis not wrong by itself (singletons are legit); flag only a per-call instance nameduseX, or every composable defaulting touseXout of reflex. - Layout lane: DOM-geometry reads (
getBoundingClientRect/offset*) belong in acreateRenderEffect(render lane), not in arefcallback (node may be pre-insert/pre-layout there). Beware the inverse "fix" too: moving a layout measure out ofcreateRenderEffectintocreateEffect/a ref on the false theory that render effects read a disconnected node — they don't; the trigger is flush-scheduled and runs after insertion. - Tests:
flush()after writes;createRootwrappers;resolve()for async settling.
Reporting
Report findings ordered by severity with file:line, the broken expectation
(one line), and the concrete 2.0 fix. Note clean areas that were checked.
For deep API verification during review, the solidjs-v2 skill's references
cover signatures; installed typings in node_modules are final word — the
betas churn the public API freely (e.g. isRefreshing was a public solid-js
export from beta.0 through beta.14, then removed in beta.15).
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.