claude-toolkit 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +54 -13
- package/bin/cli.ts +75 -19
- package/package.json +6 -7
- package/src/generator.ts +64 -26
- package/src/refresh-hint.ts +95 -0
- package/bin/postinstall.mjs +0 -69
- package/docs/README.md +0 -59
- package/docs/agents/code-reviewer.md +0 -76
- package/docs/agents/github-workflow.md +0 -98
- package/docs/best-practices/capacitor/README.md +0 -64
- package/docs/best-practices/capacitor/capacitor-8.md +0 -60
- package/docs/best-practices/capacitor/capgo-setup.md +0 -151
- package/docs/best-practices/capacitor/channels-and-rollouts.md +0 -101
- package/docs/best-practices/capacitor/live-updates-ota.md +0 -75
- package/docs/best-practices/capacitor/security-encryption.md +0 -68
- package/docs/best-practices/solidjs/README.md +0 -43
- package/docs/best-practices/solidjs/anti-patterns.md +0 -166
- package/docs/best-practices/solidjs/component-patterns.md +0 -131
- package/docs/best-practices/solidjs/context-and-global-state.md +0 -131
- package/docs/best-practices/solidjs/control-flow.md +0 -124
- package/docs/best-practices/solidjs/data-fetching.md +0 -205
- package/docs/best-practices/solidjs/effects-and-lifecycle.md +0 -113
- package/docs/best-practices/solidjs/performance.md +0 -100
- package/docs/best-practices/solidjs/props-patterns.md +0 -100
- package/docs/best-practices/solidjs/reactivity-model.md +0 -104
- package/docs/best-practices/solidjs/signals-and-state.md +0 -78
- package/docs/best-practices/solidjs/stores-and-nested-state.md +0 -111
- package/docs/best-practices/solidjs/typescript-integration.md +0 -186
- package/docs/best-practices/testing/README.md +0 -84
- package/docs/best-practices/testing/playwright-e2e.md +0 -649
- package/docs/best-practices/testing/storybook-interaction.md +0 -455
- package/docs/best-practices/testing/vitest-unit.md +0 -451
- package/docs/best-practices/typescript/README.md +0 -45
- package/docs/best-practices/typescript/any-and-unknown.md +0 -73
- package/docs/best-practices/typescript/deriving-vs-decoupling.md +0 -83
- package/docs/best-practices/typescript/discriminated-unions.md +0 -75
- package/docs/best-practices/typescript/enums-alternatives.md +0 -72
- package/docs/best-practices/typescript/essential-patterns.md +0 -119
- package/docs/best-practices/typescript/generics-patterns.md +0 -105
- package/docs/best-practices/typescript/micro-opinions.md +0 -87
- package/docs/best-practices/typescript/runtime-validation.md +0 -62
- package/docs/best-practices/typescript/satisfies-operator.md +0 -100
- package/docs/best-practices/typescript/tsconfig-cheat-sheet.md +0 -160
- package/docs/best-practices/typescript/type-organization.md +0 -64
- package/docs/best-practices/typescript/type-vs-interface.md +0 -80
- package/docs/commands/code-quality.md +0 -36
- package/docs/commands/onboard.md +0 -63
- package/docs/commands/pr-review.md +0 -91
- package/docs/commands/pr-summary.md +0 -50
- package/docs/commands/proto-check.md +0 -59
- package/docs/commands/ticket.md +0 -56
- package/docs/skills/code-style.md +0 -26
- package/docs/skills/systematic-debugging.md +0 -70
- package/docs/skills/testing-patterns.md +0 -140
- package/docs/skills/typescript-conventions.md +0 -283
- package/docs/skills/verification-before-completion.md +0 -103
- package/docs/stacks/capacitor-ota.md +0 -145
- package/docs/stacks/cloudflare-d1-kv.md +0 -144
- package/docs/stacks/esnext-idioms.md +0 -25
- package/docs/stacks/i18n-typesafe.md +0 -141
- package/docs/stacks/playwright-patterns.md +0 -179
- package/docs/stacks/protobuf-contracts.md +0 -85
- package/docs/stacks/rust-wasm-patterns.md +0 -106
- package/docs/stacks/solidjs-patterns.md +0 -110
- package/docs/stacks/storybook-patterns.md +0 -177
- package/docs/stacks/vanilla-extract-patterns.md +0 -115
- package/docs/stacks/vite-vitest-patterns.md +0 -485
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# Context & Global State
|
|
2
|
-
|
|
3
|
-
> Sources: [Context](https://docs.solidjs.com/concepts/context), [Complex State Management](https://docs.solidjs.com/guides/complex-state-management) — SolidJS Docs
|
|
4
|
-
|
|
5
|
-
## When to Use Context
|
|
6
|
-
|
|
7
|
-
Context solves **prop drilling** — passing state through many component layers that don't use it themselves. Use it for:
|
|
8
|
-
|
|
9
|
-
- Theme / appearance settings
|
|
10
|
-
- Authentication state
|
|
11
|
-
- Feature flags
|
|
12
|
-
- Any state needed by many descendants
|
|
13
|
-
|
|
14
|
-
**When NOT to use context:**
|
|
15
|
-
- If only a few components need the data, pass props directly
|
|
16
|
-
- If you can restructure the component tree to avoid drilling
|
|
17
|
-
- For truly global singletons, a module-level signal may be simpler
|
|
18
|
-
|
|
19
|
-
## Basic Context Pattern
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
import { createContext, useContext } from "solid-js";
|
|
23
|
-
import { createStore } from "solid-js/store";
|
|
24
|
-
|
|
25
|
-
// 1. Create context
|
|
26
|
-
const CounterContext = createContext<{
|
|
27
|
-
count: number;
|
|
28
|
-
increment: () => void;
|
|
29
|
-
}>();
|
|
30
|
-
|
|
31
|
-
// 2. Create provider component
|
|
32
|
-
function CounterProvider(props: ParentProps) {
|
|
33
|
-
const [state, setState] = createStore({ count: 0 });
|
|
34
|
-
const value = {
|
|
35
|
-
get count() { return state.count; },
|
|
36
|
-
increment() { setState("count", c => c + 1); },
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<CounterContext.Provider value={value}>
|
|
41
|
-
{props.children}
|
|
42
|
-
</CounterContext.Provider>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 3. Consume with useContext
|
|
47
|
-
function Counter() {
|
|
48
|
-
const ctx = useContext(CounterContext);
|
|
49
|
-
return <button onClick={ctx.increment}>{ctx.count}</button>;
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Safe useContext with Error Throwing
|
|
54
|
-
|
|
55
|
-
`useContext` returns `undefined` when no provider exists above. Create a custom hook that throws a helpful error:
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
function useCounter() {
|
|
59
|
-
const ctx = useContext(CounterContext);
|
|
60
|
-
if (!ctx) {
|
|
61
|
-
throw new Error("useCounter must be used within a CounterProvider");
|
|
62
|
-
}
|
|
63
|
-
return ctx;
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
This also **narrows the TypeScript type** — no more `| undefined`.
|
|
68
|
-
|
|
69
|
-
## Context + Store Pattern (Recommended)
|
|
70
|
-
|
|
71
|
-
Combine `createStore` with context for scalable state. Stores give you fine-grained reactivity; context distributes it without prop drilling.
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
function createTaskStore() {
|
|
75
|
-
const [state, setState] = createStore({
|
|
76
|
-
tasks: [] as Task[],
|
|
77
|
-
filter: "all" as "all" | "active" | "completed",
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
get tasks() { return state.tasks; },
|
|
82
|
-
get filter() { return state.filter; },
|
|
83
|
-
addTask(text: string) {
|
|
84
|
-
setState("tasks", state.tasks.length, {
|
|
85
|
-
id: crypto.randomUUID(),
|
|
86
|
-
text,
|
|
87
|
-
completed: false,
|
|
88
|
-
});
|
|
89
|
-
},
|
|
90
|
-
toggleTask(id: string) {
|
|
91
|
-
setState("tasks", t => t.id === id, "completed", c => !c);
|
|
92
|
-
},
|
|
93
|
-
setFilter(filter: "all" | "active" | "completed") {
|
|
94
|
-
setState("filter", filter);
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const TaskContext = createContext<ReturnType<typeof createTaskStore>>();
|
|
100
|
-
|
|
101
|
-
function TaskProvider(props: ParentProps) {
|
|
102
|
-
return (
|
|
103
|
-
<TaskContext.Provider value={createTaskStore()}>
|
|
104
|
-
{props.children}
|
|
105
|
-
</TaskContext.Provider>
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
**Why this works well in Solid:** Updating a signal at the top of the tree does NOT cause children to re-render. Only the specific reactive expressions that read the changed property update. There is no performance penalty for high-level state.
|
|
111
|
-
|
|
112
|
-
## HMR Consideration
|
|
113
|
-
|
|
114
|
-
To avoid recreating context during Hot Module Replacement, define `createContext` in its **own module** (separate file), not inline in a component file.
|
|
115
|
-
|
|
116
|
-
## Module-Level Signals vs Context
|
|
117
|
-
|
|
118
|
-
For truly global, app-wide singletons (e.g., a theme toggle), a module-level signal is simpler:
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// theme.ts
|
|
122
|
-
export const [theme, setTheme] = createSignal<"light" | "dark">("light");
|
|
123
|
-
|
|
124
|
-
// Any component can import directly
|
|
125
|
-
import { theme, setTheme } from "./theme";
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Use context instead when:
|
|
129
|
-
- Different subtrees need different values (e.g., per-user state)
|
|
130
|
-
- You need testability (mock providers in tests)
|
|
131
|
-
- The state has complex dependencies
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# Control Flow
|
|
2
|
-
|
|
3
|
-
> Sources: [Show](https://docs.solidjs.com/reference/components/show), [For](https://docs.solidjs.com/reference/components/for), [SolidJS Tutorials](https://www.solidjs.com/tutorial) — SolidJS Docs
|
|
4
|
-
|
|
5
|
-
## Why Control Flow Components?
|
|
6
|
-
|
|
7
|
-
In Solid, **avoid JavaScript-native conditionals and `.map()` in JSX**. Solid's control flow components provide explicit boundaries that the compiler can optimize — they track dependencies and minimize DOM operations.
|
|
8
|
-
|
|
9
|
-
## `<Show>` — Conditional Rendering
|
|
10
|
-
|
|
11
|
-
Renders children when `when` is truthy. Optionally provides a `fallback`.
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
<Show when={user()} fallback={<LoginPrompt />}>
|
|
15
|
-
<Dashboard />
|
|
16
|
-
</Show>
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
### Callback Form for Type Narrowing
|
|
20
|
-
|
|
21
|
-
The callback form provides a non-null accessor — essential for TypeScript:
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
<Show when={user()}>
|
|
25
|
-
{(nonNullUser) => <span>{nonNullUser().name}</span>}
|
|
26
|
-
</Show>
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
TypeScript cannot narrow accessor types through control flow, so the callback form is the idiomatic way to access narrowed values.
|
|
30
|
-
|
|
31
|
-
## `<For>` — List Rendering
|
|
32
|
-
|
|
33
|
-
Renders a list, keyed by **reference** (not index). Only re-renders items that actually change.
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
<For each={items()}>
|
|
37
|
-
{(item, index) => (
|
|
38
|
-
<li>{index()}: {item.name}</li>
|
|
39
|
-
)}
|
|
40
|
-
</For>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
- `item` is the value (not reactive for primitives)
|
|
44
|
-
- `index()` is a signal — call it as a function
|
|
45
|
-
- Items are tracked by reference — avoid recreating objects
|
|
46
|
-
|
|
47
|
-
### `<Index>` — Keyed by Index
|
|
48
|
-
|
|
49
|
-
For primitive arrays where values change but positions are stable:
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
<Index each={names()}>
|
|
53
|
-
{(name, i) => <li>{i}: {name()}</li>}
|
|
54
|
-
</Index>
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
- `name` is a signal (reactive)
|
|
58
|
-
- `i` is a plain number (not reactive)
|
|
59
|
-
|
|
60
|
-
**Rule of thumb:** Use `<For>` for arrays of objects, `<Index>` for arrays of primitives.
|
|
61
|
-
|
|
62
|
-
## `<Switch>` / `<Match>` — Multi-Branch Conditional
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
<Switch fallback={<p>Unknown state</p>}>
|
|
66
|
-
<Match when={state() === "loading"}>
|
|
67
|
-
<Spinner />
|
|
68
|
-
</Match>
|
|
69
|
-
<Match when={state() === "error"}>
|
|
70
|
-
<ErrorDisplay />
|
|
71
|
-
</Match>
|
|
72
|
-
<Match when={state() === "ready"}>
|
|
73
|
-
<Content />
|
|
74
|
-
</Match>
|
|
75
|
-
</Switch>
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
First matching `<Match>` wins. Use `fallback` on `<Switch>` for the default case.
|
|
79
|
-
|
|
80
|
-
## `<ErrorBoundary>` — Error Catching
|
|
81
|
-
|
|
82
|
-
Catches errors thrown in child components and renders a fallback:
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
<ErrorBoundary fallback={(err, reset) => (
|
|
86
|
-
<div>
|
|
87
|
-
<p>Error: {err.message}</p>
|
|
88
|
-
<button onClick={reset}>Retry</button>
|
|
89
|
-
</div>
|
|
90
|
-
)}>
|
|
91
|
-
<RiskyComponent />
|
|
92
|
-
</ErrorBoundary>
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
The `reset` function re-renders the children, giving the component a fresh chance.
|
|
96
|
-
|
|
97
|
-
## `<Suspense>` — Async Loading States
|
|
98
|
-
|
|
99
|
-
Displays a fallback while waiting for async resources to resolve:
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
<Suspense fallback={<Skeleton />}>
|
|
103
|
-
<UserProfile /> {/* Uses createResource internally */}
|
|
104
|
-
</Suspense>
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
Suspense detects any `createResource` read within its boundary and holds rendering until all resolve. Nested Suspense boundaries allow granular loading states.
|
|
108
|
-
|
|
109
|
-
## `<Dynamic>` — Dynamic Component
|
|
110
|
-
|
|
111
|
-
Render a component determined at runtime:
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
<Dynamic component={isAdmin() ? AdminPanel : UserPanel} user={user()} />
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## Don't Use
|
|
118
|
-
|
|
119
|
-
| Instead of | Use |
|
|
120
|
-
|---|---|
|
|
121
|
-
| `{condition && <Component />}` | `<Show when={condition}>` |
|
|
122
|
-
| `{condition ? <A /> : <B />}` | `<Show when={condition} fallback={<B />}>` |
|
|
123
|
-
| `{items.map(i => ...)}` | `<For each={items()}>` |
|
|
124
|
-
| Nested ternaries | `<Switch>` / `<Match>` |
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
# Data Fetching
|
|
2
|
-
|
|
3
|
-
> Sources: [Fetching Data](https://docs.solidjs.com/guides/fetching-data), [createResource](https://docs.solidjs.com/reference/basic-reactivity/create-resource) — SolidJS Docs
|
|
4
|
-
|
|
5
|
-
## createResource
|
|
6
|
-
|
|
7
|
-
The primary tool for async data in SolidJS. Wraps an async operation into a reactive signal with built-in loading, error, and state tracking.
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
const [user, { mutate, refetch }] = createResource(userId, async (id) => {
|
|
11
|
-
const res = await fetch(`/api/users/${id}`);
|
|
12
|
-
return res.json();
|
|
13
|
-
});
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
- `userId` is the **source signal** — when it changes, the fetcher re-runs
|
|
17
|
-
- The fetcher receives the source value as its first argument
|
|
18
|
-
- Returns a resource signal with reactive properties
|
|
19
|
-
|
|
20
|
-
### Resource Properties
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
user() // The resolved data (or undefined)
|
|
24
|
-
user.loading // Boolean — is a fetch in progress?
|
|
25
|
-
user.error // Error object if the fetch failed
|
|
26
|
-
user.state // "unresolved" | "pending" | "ready" | "refreshing" | "errored"
|
|
27
|
-
user.latest // Most recent successful data (persists during refetch)
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Handling States in JSX
|
|
31
|
-
|
|
32
|
-
### With Control Flow
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
<Show when={!user.loading} fallback={<Spinner />}>
|
|
36
|
-
<Show when={!user.error} fallback={<Error message={user.error.message} />}>
|
|
37
|
-
<UserCard user={user()} />
|
|
38
|
-
</Show>
|
|
39
|
-
</Show>
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### With Suspense + ErrorBoundary (recommended)
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
<ErrorBoundary fallback={(err, reset) => (
|
|
46
|
-
<div>
|
|
47
|
-
<p>Failed: {err.message}</p>
|
|
48
|
-
<button onClick={reset}>Retry</button>
|
|
49
|
-
</div>
|
|
50
|
-
)}>
|
|
51
|
-
<Suspense fallback={<Skeleton />}>
|
|
52
|
-
<UserCard /> {/* reads createResource internally */}
|
|
53
|
-
</Suspense>
|
|
54
|
-
</ErrorBoundary>
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Suspense detects resource reads within its boundary and shows the fallback until all resolve. This is the idiomatic pattern.
|
|
58
|
-
|
|
59
|
-
## Optimistic Updates with `mutate`
|
|
60
|
-
|
|
61
|
-
Update the UI immediately, then let the server catch up:
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
const [tasks, { mutate, refetch }] = createResource(fetchTasks);
|
|
65
|
-
|
|
66
|
-
function addTask(text: string) {
|
|
67
|
-
// Optimistic: update UI immediately
|
|
68
|
-
mutate(prev => [...(prev ?? []), { id: "temp", text, completed: false }]);
|
|
69
|
-
|
|
70
|
-
// Then sync with server
|
|
71
|
-
postTask(text).then(() => refetch());
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
`mutate` overwrites the resource value without calling the fetcher.
|
|
76
|
-
|
|
77
|
-
## Refetching
|
|
78
|
-
|
|
79
|
-
Force a refetch independent of the source signal:
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
const [prices, { refetch }] = createResource(fetchPrices);
|
|
83
|
-
|
|
84
|
-
// Poll every 10 seconds
|
|
85
|
-
const interval = setInterval(refetch, 10_000);
|
|
86
|
-
onCleanup(() => clearInterval(interval));
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## createAsync (SolidStart / Solid Router)
|
|
90
|
-
|
|
91
|
-
For SolidStart apps, `createAsync` is the recommended async primitive. It's a thin wrapper over `createResource` designed for router-integrated data loading:
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
import { createAsync } from "@solidjs/router";
|
|
95
|
-
|
|
96
|
-
const user = createAsync(() => getUser());
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
`createAsync` is intended to become the standard async primitive in Solid 2.0.
|
|
100
|
-
|
|
101
|
-
## Runtime Validation with Zod / Valibot
|
|
102
|
-
|
|
103
|
-
Data entering your app from APIs, forms, or `localStorage` should be validated at runtime — TypeScript types don't exist at runtime. See [TypeScript Runtime Validation](../typescript/runtime-validation.md) for when to validate.
|
|
104
|
-
|
|
105
|
-
### Validating API Responses in `createResource`
|
|
106
|
-
|
|
107
|
-
Parse and type-narrow data inside the fetcher so the resource signal is guaranteed to hold valid data:
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
import { z } from "zod";
|
|
111
|
-
|
|
112
|
-
const UserSchema = z.object({
|
|
113
|
-
id: z.string(),
|
|
114
|
-
name: z.string(),
|
|
115
|
-
email: z.string().email(),
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
type User = z.infer<typeof UserSchema>;
|
|
119
|
-
|
|
120
|
-
const [user] = createResource(userId, async (id): Promise<User> => {
|
|
121
|
-
const res = await fetch(`/api/users/${id}`);
|
|
122
|
-
const json = await res.json();
|
|
123
|
-
return UserSchema.parse(json); // Throws on invalid shape
|
|
124
|
-
});
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
`z.infer<typeof Schema>` keeps your TypeScript types and runtime validation in sync — no duplication.
|
|
128
|
-
|
|
129
|
-
### A Reusable Typed Fetch Helper
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
const fetchTyped = async <T extends z.ZodSchema>(
|
|
133
|
-
url: string,
|
|
134
|
-
schema: T,
|
|
135
|
-
): Promise<z.infer<T>> => {
|
|
136
|
-
const res = await fetch(url);
|
|
137
|
-
return schema.parse(await res.json());
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// Usage with createResource
|
|
141
|
-
const [user] = createResource(userId, (id) =>
|
|
142
|
-
fetchTyped(`/api/users/${id}`, UserSchema)
|
|
143
|
-
);
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Form Validation
|
|
147
|
-
|
|
148
|
-
[@modular-forms/solid](https://modularforms.dev/solid/guides/validate-your-fields) has built-in Zod integration via the `zodForm` adapter:
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
import { createForm } from "@modular-forms/solid";
|
|
152
|
-
import { zodForm } from "@modular-forms/solid";
|
|
153
|
-
|
|
154
|
-
const LoginSchema = z.object({
|
|
155
|
-
email: z.string().min(1, "Required").email("Invalid email"),
|
|
156
|
-
password: z.string().min(8, "Min 8 characters"),
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
type LoginForm = z.infer<typeof LoginSchema>;
|
|
160
|
-
|
|
161
|
-
const [form, { Form, Field }] = createForm<LoginForm>({
|
|
162
|
-
validate: zodForm(LoginSchema),
|
|
163
|
-
});
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Valibot as a Lighter Alternative
|
|
167
|
-
|
|
168
|
-
[Valibot](https://valibot.dev/) provides the same validation patterns with a tree-shakeable design — ~1.4kb vs Zod's ~13kb for a typical form schema. Ryan Carniato (SolidJS creator) has endorsed it, and `@modular-forms/solid` supports it natively via `valiForm`.
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
import * as v from "valibot";
|
|
172
|
-
|
|
173
|
-
const UserSchema = v.object({
|
|
174
|
-
id: v.string(),
|
|
175
|
-
name: v.pipe(v.string(), v.nonEmpty()),
|
|
176
|
-
email: v.pipe(v.string(), v.email()),
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
type User = v.InferInput<typeof UserSchema>;
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
**Choose based on your constraints:**
|
|
183
|
-
|
|
184
|
-
| Criteria | Zod | Valibot |
|
|
185
|
-
| --------------- | --------- | ---------- |
|
|
186
|
-
| Bundle size | ~13kb | ~1.4kb |
|
|
187
|
-
| Tree-shakeable | No | Yes |
|
|
188
|
-
| Ecosystem | Larger | Growing |
|
|
189
|
-
| API style | Chaining | Functional |
|
|
190
|
-
| Runtime speed | Baseline | ~2x faster |
|
|
191
|
-
|
|
192
|
-
## Don't Fetch in Effects
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
// WRONG — no loading state, no error handling, race conditions
|
|
196
|
-
createEffect(async () => {
|
|
197
|
-
const data = await fetch(`/api/${id()}`);
|
|
198
|
-
setResult(await data.json());
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// RIGHT — built-in state management, cancellation, reactivity
|
|
202
|
-
const [result] = createResource(id, fetchData);
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Effects run after render, causing a flash of empty state. Resources integrate with Suspense and provide proper loading/error states.
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
# Effects & Lifecycle
|
|
2
|
-
|
|
3
|
-
> Sources: [Effects](https://docs.solidjs.com/concepts/effects), [onMount](https://docs.solidjs.com/reference/lifecycle/on-mount), [onCleanup](https://docs.solidjs.com/reference/lifecycle/on-cleanup) — SolidJS Docs
|
|
4
|
-
|
|
5
|
-
## createEffect
|
|
6
|
-
|
|
7
|
-
Runs a side effect whenever its tracked dependencies change. Dependencies are automatically detected.
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
createEffect(() => {
|
|
11
|
-
document.title = `${count()} items`;
|
|
12
|
-
});
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
### When to Use Effects
|
|
16
|
-
|
|
17
|
-
Effects are for **interacting with the outside world** — the DOM, third-party libraries, logging, WebSockets.
|
|
18
|
-
|
|
19
|
-
**Use effects for:**
|
|
20
|
-
- DOM manipulation not covered by JSX
|
|
21
|
-
- Third-party library integration
|
|
22
|
-
- WebSocket connections
|
|
23
|
-
- Logging / analytics
|
|
24
|
-
|
|
25
|
-
**Don't use effects for:**
|
|
26
|
-
- Derived values → use `createMemo` or inline derivation
|
|
27
|
-
- State synchronization → derive instead of sync
|
|
28
|
-
- Data fetching → use `createResource`
|
|
29
|
-
|
|
30
|
-
### The #1 Anti-Pattern: Syncing State
|
|
31
|
-
|
|
32
|
-
```typescript
|
|
33
|
-
// WRONG — creates glitchy intermediate states
|
|
34
|
-
createEffect(() => {
|
|
35
|
-
setDoubled(count() * 2);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// RIGHT — derive the value
|
|
39
|
-
const doubled = createMemo(() => count() * 2);
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
When you use effects to sync state, the reactive graph must choose an execution order, which can produce inconsistent intermediate states that flash in the UI.
|
|
43
|
-
|
|
44
|
-
## `on` — Explicit Dependencies
|
|
45
|
-
|
|
46
|
-
When you need to control which signals trigger an effect:
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
import { on } from "solid-js";
|
|
50
|
-
|
|
51
|
-
createEffect(on(userId, (id) => {
|
|
52
|
-
// Only runs when userId changes, not when other signals read inside change
|
|
53
|
-
fetchUserData(id);
|
|
54
|
-
}));
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
`on` also provides the previous value:
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
createEffect(on(count, (current, previous) => {
|
|
61
|
-
console.log(`Changed from ${previous} to ${current}`);
|
|
62
|
-
}));
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## onMount
|
|
66
|
-
|
|
67
|
-
Runs once after the component's initial render, when DOM elements are available. Non-reactive — it doesn't track signals.
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
onMount(() => {
|
|
71
|
-
// DOM is ready, refs are populated
|
|
72
|
-
inputRef?.focus();
|
|
73
|
-
|
|
74
|
-
// Good for one-time setup
|
|
75
|
-
const chart = new Chart(canvasRef);
|
|
76
|
-
});
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
**Use for:** ref access, one-time DOM setup, measurements, API calls that should only happen once.
|
|
80
|
-
|
|
81
|
-
## onCleanup
|
|
82
|
-
|
|
83
|
-
Runs when the enclosing reactive scope is disposed or re-executed. Essential for preventing memory leaks.
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
// In a component — runs when component unmounts
|
|
87
|
-
onCleanup(() => {
|
|
88
|
-
chart.destroy();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// In an effect — runs before each re-execution and on dispose
|
|
92
|
-
createEffect(() => {
|
|
93
|
-
const id = setInterval(() => tick(), props.interval);
|
|
94
|
-
onCleanup(() => clearInterval(id));
|
|
95
|
-
});
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
**Always clean up:**
|
|
99
|
-
- Event listeners
|
|
100
|
-
- Timers (`setInterval`, `setTimeout`)
|
|
101
|
-
- WebSocket connections
|
|
102
|
-
- Third-party library instances
|
|
103
|
-
- Subscriptions
|
|
104
|
-
|
|
105
|
-
## Lifecycle Order
|
|
106
|
-
|
|
107
|
-
1. Component function runs (synchronous)
|
|
108
|
-
2. Reactive expressions are set up (signals, memos, effects registered)
|
|
109
|
-
3. DOM is created and mounted
|
|
110
|
-
4. `onMount` callbacks fire
|
|
111
|
-
5. Effects run (in dependency order)
|
|
112
|
-
6. On signal change: effects re-run, `onCleanup` fires before each re-run
|
|
113
|
-
7. On unmount: all `onCleanup` callbacks fire
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# Performance
|
|
2
|
-
|
|
3
|
-
> Sources: [lazy](https://docs.solidjs.com/reference/component-apis/lazy), [batch](https://docs.solidjs.com/reference/reactive-utilities/batch), [untrack](https://docs.solidjs.com/reference/reactive-utilities/untrack) — SolidJS Docs
|
|
4
|
-
|
|
5
|
-
## What Solid Optimizes For You
|
|
6
|
-
|
|
7
|
-
Before reaching for performance tools, understand what Solid already does:
|
|
8
|
-
|
|
9
|
-
- **No virtual DOM diffing** — updates go directly to the specific DOM nodes
|
|
10
|
-
- **Components run once** — no re-renders, no stale closures
|
|
11
|
-
- **Automatic batching in stores** — multiple `setState` calls in the same synchronous block are batched
|
|
12
|
-
- **Conditional dependency tracking** — branches not taken don't create subscriptions
|
|
13
|
-
- **Equality checks on signals** — redundant updates are ignored
|
|
14
|
-
|
|
15
|
-
Most performance problems in Solid come from **fighting the reactive model**, not from the framework itself.
|
|
16
|
-
|
|
17
|
-
## `batch` — Group Signal Updates
|
|
18
|
-
|
|
19
|
-
When updating multiple independent signals in one synchronous operation, `batch` prevents intermediate re-renders:
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
import { batch } from "solid-js";
|
|
23
|
-
|
|
24
|
-
batch(() => {
|
|
25
|
-
setFirstName("John");
|
|
26
|
-
setLastName("Doe");
|
|
27
|
-
setAge(30);
|
|
28
|
-
});
|
|
29
|
-
// All effects that depend on these signals run once, not three times
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Without `batch`, each setter triggers its dependents immediately. With `batch`, all updates are applied, then dependents run once with the final state.
|
|
33
|
-
|
|
34
|
-
**Note:** Store `setState` calls are already batched automatically. `batch` is primarily useful for multiple `createSignal` setters.
|
|
35
|
-
|
|
36
|
-
## `untrack` — Read Without Subscribing
|
|
37
|
-
|
|
38
|
-
Read a reactive value without creating a dependency:
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
import { untrack } from "solid-js";
|
|
42
|
-
|
|
43
|
-
createEffect(() => {
|
|
44
|
-
// This effect re-runs when query() changes
|
|
45
|
-
// But does NOT re-run when options() changes
|
|
46
|
-
fetchData(query(), untrack(() => options()));
|
|
47
|
-
});
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
**Use for:**
|
|
51
|
-
- Static configuration that shouldn't trigger re-runs
|
|
52
|
-
- Initial values you read once
|
|
53
|
-
- Avoiding circular dependencies
|
|
54
|
-
|
|
55
|
-
## `lazy` — Code Splitting
|
|
56
|
-
|
|
57
|
-
Lazy-load components to reduce initial bundle size:
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
import { lazy } from "solid-js";
|
|
61
|
-
|
|
62
|
-
const AdminPanel = lazy(() => import("./AdminPanel"));
|
|
63
|
-
|
|
64
|
-
// Renders like a normal component
|
|
65
|
-
// Loads the code on first render, triggers Suspense boundaries
|
|
66
|
-
<Suspense fallback={<Spinner />}>
|
|
67
|
-
<AdminPanel />
|
|
68
|
-
</Suspense>
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
The component loads on first render, not on import. Combine with `<Suspense>` for loading states.
|
|
72
|
-
|
|
73
|
-
## `<Dynamic>` — Avoid Mounting All Variants
|
|
74
|
-
|
|
75
|
-
Instead of conditionally showing/hiding multiple heavy components, use `<Dynamic>` to mount only the active one:
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
const panels = { settings: Settings, profile: Profile, billing: Billing };
|
|
79
|
-
|
|
80
|
-
<Dynamic component={panels[activePanel()]} />
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Performance Anti-Patterns
|
|
84
|
-
|
|
85
|
-
| Anti-Pattern | Why It's Slow | Fix |
|
|
86
|
-
|---|---|---|
|
|
87
|
-
| Recreating objects in signals | Triggers all subscribers on every set | Use stores for nested data |
|
|
88
|
-
| Using `.map()` instead of `<For>` | Recreates all DOM nodes on any change | Use `<For>` or `<Index>` |
|
|
89
|
-
| Effects that sync state | Creates cascading updates | Derive with `createMemo` |
|
|
90
|
-
| Reading signals at component top level | Captures once, never updates | Read in JSX or effects |
|
|
91
|
-
| Wrapping everything in effects | Unnecessary tracking overhead | Only for external side effects |
|
|
92
|
-
|
|
93
|
-
## When to Actually Worry
|
|
94
|
-
|
|
95
|
-
Solid's reactivity is already very fast. Optimize only when you observe actual performance issues:
|
|
96
|
-
|
|
97
|
-
1. **Lists with 1000+ items** — ensure you're using `<For>` not `.map()`
|
|
98
|
-
2. **Rapid signal updates** (e.g., mouse move) — consider `batch` or throttling
|
|
99
|
-
3. **Large initial bundle** — use `lazy` for routes and heavy components
|
|
100
|
-
4. **Deep store trees** — use path syntax updates, not object replacement
|