claude-toolkit 0.1.9
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 +38 -0
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/bin/cli.ts +112 -0
- package/core/agents/ct-code-reviewer.md +123 -0
- package/core/agents/ct-github-workflow.md +137 -0
- package/core/commands/ct/code-quality.md +59 -0
- package/core/commands/ct/onboard.md +84 -0
- package/core/commands/ct/pr-review.md +104 -0
- package/core/commands/ct/pr-summary.md +59 -0
- package/core/commands/ct/proto-check.md +74 -0
- package/core/commands/ct/ticket.md +71 -0
- package/core/hooks/skill-eval.js +381 -0
- package/core/hooks/skill-eval.sh +35 -0
- package/core/hooks/skill-rules.schema.json +112 -0
- package/core/skills/systematic-debugging/SKILL.md +44 -0
- package/core/skills/testing-patterns/SKILL.md +52 -0
- package/core/skills/typescript-conventions/SKILL.md +57 -0
- package/core/skills/verification-before-completion/SKILL.md +42 -0
- package/docs/README.md +49 -0
- package/docs/agents/code-reviewer.md +76 -0
- package/docs/agents/github-workflow.md +98 -0
- package/docs/best-practices/solidjs/README.md +43 -0
- package/docs/best-practices/solidjs/anti-patterns.md +166 -0
- package/docs/best-practices/solidjs/component-patterns.md +131 -0
- package/docs/best-practices/solidjs/context-and-global-state.md +131 -0
- package/docs/best-practices/solidjs/control-flow.md +124 -0
- package/docs/best-practices/solidjs/data-fetching.md +205 -0
- package/docs/best-practices/solidjs/effects-and-lifecycle.md +113 -0
- package/docs/best-practices/solidjs/performance.md +100 -0
- package/docs/best-practices/solidjs/props-patterns.md +100 -0
- package/docs/best-practices/solidjs/reactivity-model.md +104 -0
- package/docs/best-practices/solidjs/signals-and-state.md +78 -0
- package/docs/best-practices/solidjs/stores-and-nested-state.md +111 -0
- package/docs/best-practices/solidjs/typescript-integration.md +186 -0
- package/docs/best-practices/typescript/README.md +45 -0
- package/docs/best-practices/typescript/any-and-unknown.md +73 -0
- package/docs/best-practices/typescript/deriving-vs-decoupling.md +83 -0
- package/docs/best-practices/typescript/discriminated-unions.md +75 -0
- package/docs/best-practices/typescript/enums-alternatives.md +72 -0
- package/docs/best-practices/typescript/essential-patterns.md +119 -0
- package/docs/best-practices/typescript/generics-patterns.md +105 -0
- package/docs/best-practices/typescript/micro-opinions.md +87 -0
- package/docs/best-practices/typescript/runtime-validation.md +62 -0
- package/docs/best-practices/typescript/satisfies-operator.md +100 -0
- package/docs/best-practices/typescript/tsconfig-cheat-sheet.md +129 -0
- package/docs/best-practices/typescript/type-organization.md +64 -0
- package/docs/best-practices/typescript/type-vs-interface.md +80 -0
- package/docs/commands/code-quality.md +42 -0
- package/docs/commands/onboard.md +72 -0
- package/docs/commands/pr-review.md +102 -0
- package/docs/commands/pr-summary.md +50 -0
- package/docs/commands/proto-check.md +59 -0
- package/docs/commands/ticket.md +56 -0
- package/docs/skills/systematic-debugging.md +70 -0
- package/docs/skills/testing-patterns.md +89 -0
- package/docs/skills/typescript-conventions.md +137 -0
- package/docs/skills/verification-before-completion.md +91 -0
- package/docs/stacks/cloudflare-d1-kv.md +110 -0
- package/docs/stacks/i18n-typesafe.md +141 -0
- package/docs/stacks/protobuf-contracts.md +85 -0
- package/docs/stacks/rust-wasm-patterns.md +106 -0
- package/docs/stacks/solidjs-patterns.md +110 -0
- package/docs/stacks/vanilla-extract-patterns.md +115 -0
- package/package.json +58 -0
- package/src/generator.ts +317 -0
- package/src/index.ts +30 -0
- package/src/types.ts +85 -0
- package/src/utils.ts +53 -0
- package/stacks/cloudflare/skills/cloudflare-d1-kv/SKILL.md +84 -0
- package/stacks/cloudflare/stack.json +26 -0
- package/stacks/i18n-typesafe/skills/i18n-typesafe/SKILL.md +64 -0
- package/stacks/i18n-typesafe/stack.json +25 -0
- package/stacks/protobuf/skills/protobuf-contracts/SKILL.md +78 -0
- package/stacks/protobuf/stack.json +25 -0
- package/stacks/rust-wasm/skills/rust-wasm-patterns/SKILL.md +76 -0
- package/stacks/rust-wasm/stack.json +26 -0
- package/stacks/solidjs/skills/solidjs-patterns/SKILL.md +66 -0
- package/stacks/solidjs/stack.json +52 -0
- package/stacks/vanilla-extract/skills/vanilla-extract-patterns/SKILL.md +76 -0
- package/stacks/vanilla-extract/stack.json +40 -0
- package/templates/claude-toolkit.config.ts +34 -0
- package/templates/configs/biome.base.json +35 -0
- package/templates/configs/tsconfig.base.json +16 -0
- package/templates/skill-rules.base.json +98 -0
|
@@ -0,0 +1,205 @@
|
|
|
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.
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
|
@@ -0,0 +1,100 @@
|
|
|
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
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Props Patterns
|
|
2
|
+
|
|
3
|
+
> Sources: [Props](https://docs.solidjs.com/concepts/components/props), [Props Tutorials](https://www.solidjs.com/tutorial/props_defaults) — SolidJS Docs
|
|
4
|
+
|
|
5
|
+
## The Cardinal Rule: Never Destructure Props
|
|
6
|
+
|
|
7
|
+
Props use **getters** internally for reactive tracking. Destructuring extracts the value once, severing the reactive connection permanently.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// WRONG — breaks reactivity
|
|
11
|
+
const MyComponent = ({ name, count }) => {
|
|
12
|
+
return <div>{name}: {count}</div>; // Never updates
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// WRONG — same problem
|
|
16
|
+
const MyComponent = (props) => {
|
|
17
|
+
const { name } = props; // Captured once, never reactive
|
|
18
|
+
return <div>{name}</div>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// CORRECT — preserves reactivity
|
|
22
|
+
const MyComponent = (props) => {
|
|
23
|
+
return <div>{props.name}: {props.count}</div>;
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## `mergeProps` for Defaults
|
|
28
|
+
|
|
29
|
+
Set default prop values without breaking reactivity:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { mergeProps } from "solid-js";
|
|
33
|
+
|
|
34
|
+
const Button = (props) => {
|
|
35
|
+
const merged = mergeProps({ variant: "primary", size: "md" }, props);
|
|
36
|
+
return <button class={`btn-${merged.variant} btn-${merged.size}`}>
|
|
37
|
+
{merged.children}
|
|
38
|
+
</button>;
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`mergeProps` resolves properties in reverse order — later objects override earlier ones while preserving reactive tracking.
|
|
43
|
+
|
|
44
|
+
## `splitProps` for Prop Forwarding
|
|
45
|
+
|
|
46
|
+
Safely separate local props from props to forward to children:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { splitProps } from "solid-js";
|
|
50
|
+
|
|
51
|
+
const Input = (props) => {
|
|
52
|
+
const [local, inputProps] = splitProps(props, ["label", "error"]);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div>
|
|
56
|
+
<label>{local.label}</label>
|
|
57
|
+
<input {...inputProps} />
|
|
58
|
+
<Show when={local.error}>
|
|
59
|
+
<span class="error">{local.error}</span>
|
|
60
|
+
</Show>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
You can split into multiple groups:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const [style, events, rest] = splitProps(props, ["class", "style"], ["onClick", "onInput"]);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## `children` Helper
|
|
73
|
+
|
|
74
|
+
Accessing `props.children` multiple times can create duplicate elements. Use the `children` helper to resolve children once:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { children } from "solid-js";
|
|
78
|
+
|
|
79
|
+
const ColoredList = (props) => {
|
|
80
|
+
const resolved = children(() => props.children);
|
|
81
|
+
|
|
82
|
+
return <div class="colored">{resolved()}</div>;
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The helper resolves any dynamic children into a flat array, memoized for safe repeated access.
|
|
87
|
+
|
|
88
|
+
## Call Signals Before Passing as Props
|
|
89
|
+
|
|
90
|
+
When passing a signal value to a child component, call the getter. Components shouldn't need to know whether a prop came from a signal or a static value.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// CORRECT — child receives a plain value
|
|
94
|
+
<UserCard name={userName()} age={userAge()} />
|
|
95
|
+
|
|
96
|
+
// Also valid — passing a reactive getter for the child to track
|
|
97
|
+
<UserCard name={userName} /> // child would read props.name()
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The first pattern is simpler and recommended for most cases. The second is useful when you want the child to participate in fine-grained tracking.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Reactivity Model
|
|
2
|
+
|
|
3
|
+
> Sources: [Fine-Grained Reactivity](https://docs.solidjs.com/advanced-concepts/fine-grained-reactivity), [Intro to Reactivity](https://docs.solidjs.com/concepts/intro-to-reactivity) — SolidJS Docs
|
|
4
|
+
|
|
5
|
+
## The Core Difference
|
|
6
|
+
|
|
7
|
+
SolidJS uses **fine-grained reactivity**. Updates target specific DOM elements that depend on changed data — not entire component trees. Components run **once**; only the reactive expressions inside them re-execute.
|
|
8
|
+
|
|
9
|
+
This means:
|
|
10
|
+
- No virtual DOM diffing
|
|
11
|
+
- No component re-renders
|
|
12
|
+
- No stale closure bugs
|
|
13
|
+
- Updates are surgical and automatic
|
|
14
|
+
|
|
15
|
+
## How It Works
|
|
16
|
+
|
|
17
|
+
### Signals, Effects, and the Subscription Model
|
|
18
|
+
|
|
19
|
+
The reactive system has two key elements: **signals** (data sources) and **observers** (effects, memos, JSX expressions).
|
|
20
|
+
|
|
21
|
+
1. A global `currentSubscriber` variable tracks the active observer
|
|
22
|
+
2. When an observer runs, it sets itself as the current subscriber
|
|
23
|
+
3. Any signal read during that execution registers the subscriber
|
|
24
|
+
4. When a signal's value changes, all registered subscribers re-execute
|
|
25
|
+
5. The subscriber reference is cleared after execution
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
const [count, setCount] = createSignal(0);
|
|
29
|
+
|
|
30
|
+
// This effect becomes a subscriber of `count`
|
|
31
|
+
createEffect(() => {
|
|
32
|
+
console.log(count()); // Reading count() registers the dependency
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
setCount(1); // Notifies the effect, which re-runs
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Equality Checks
|
|
39
|
+
|
|
40
|
+
Signals only notify subscribers when the value **actually changes**. Redundant updates (setting the same value) are ignored.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
setCount(0); // If count is already 0, no effects fire
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## The Synchronous Contract
|
|
47
|
+
|
|
48
|
+
Reactivity in Solid is **synchronous**. The system registers a subscriber, runs the function, and unregisters — all in one synchronous pass.
|
|
49
|
+
|
|
50
|
+
This means **async operations break tracking**:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// BROKEN — fetch happens after tracking ends
|
|
54
|
+
createEffect(async () => {
|
|
55
|
+
const data = await fetch(url()); // url() is tracked
|
|
56
|
+
setResult(data); // but this runs after tracking ends
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// CORRECT — use createResource for async
|
|
60
|
+
const [data] = createResource(url, fetchData);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Dependency Tracking is Automatic
|
|
64
|
+
|
|
65
|
+
You don't declare dependencies. Solid tracks them by observing which signals are read during execution:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
createEffect(() => {
|
|
69
|
+
// Solid automatically knows this depends on firstName() and lastName()
|
|
70
|
+
console.log(`${firstName()} ${lastName()}`);
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Conditional Tracking
|
|
75
|
+
|
|
76
|
+
If a branch isn't taken, signals in that branch aren't tracked:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const display = createMemo(() => {
|
|
80
|
+
if (!showDetails()) return "Hidden";
|
|
81
|
+
// name() and age() are only tracked when showDetails() is true
|
|
82
|
+
return `${name()}, age ${age()}`;
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Derive, Don't Sync
|
|
87
|
+
|
|
88
|
+
The golden rule of Solid reactivity:
|
|
89
|
+
|
|
90
|
+
**If a value can be computed from other reactive values, derive it — don't synchronize it with an effect.**
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// WRONG — synchronizing state
|
|
94
|
+
createEffect(() => {
|
|
95
|
+
setDoubled(count() * 2);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// RIGHT — deriving state
|
|
99
|
+
const doubled = createMemo(() => count() * 2);
|
|
100
|
+
// or simply:
|
|
101
|
+
const doubled = () => count() * 2;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Derived values integrate naturally with the reactive graph. Effects that synchronize state create ordering problems and glitchy intermediate states.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Signals & State
|
|
2
|
+
|
|
3
|
+
> Sources: [Signals](https://docs.solidjs.com/concepts/signals), [Derived Signals](https://docs.solidjs.com/concepts/derived-values/derived-signals) — SolidJS Docs
|
|
4
|
+
|
|
5
|
+
## createSignal
|
|
6
|
+
|
|
7
|
+
The primary state primitive. Returns a getter function and a setter function.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
const [count, setCount] = createSignal(0);
|
|
11
|
+
|
|
12
|
+
// Read: always call as a function
|
|
13
|
+
console.log(count()); // 0
|
|
14
|
+
|
|
15
|
+
// Write: direct value or updater function
|
|
16
|
+
setCount(5);
|
|
17
|
+
setCount(prev => prev + 1);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Key mental model:** The getter is a function, not a value. You must call `count()` to read it. Forgetting the parentheses is the most common Solid mistake.
|
|
21
|
+
|
|
22
|
+
## Derived Values
|
|
23
|
+
|
|
24
|
+
### Inline Derivations
|
|
25
|
+
|
|
26
|
+
For simple computations, a plain function is sufficient:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
const doubled = () => count() * 2;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This re-evaluates every time it's called in a reactive context. No caching.
|
|
33
|
+
|
|
34
|
+
### createMemo
|
|
35
|
+
|
|
36
|
+
For expensive computations or values read in multiple places, use `createMemo` to cache the result:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
const sorted = createMemo(() => {
|
|
40
|
+
console.log("Sorting..."); // Only runs when items() changes
|
|
41
|
+
return items().slice().sort((a, b) => a.localeCompare(b));
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**When to use `createMemo` vs inline derivation:**
|
|
46
|
+
|
|
47
|
+
| Scenario | Use |
|
|
48
|
+
|---|---|
|
|
49
|
+
| Simple, cheap computation | Inline function `() => ...` |
|
|
50
|
+
| Expensive computation (sort, filter, map) | `createMemo` |
|
|
51
|
+
| Value read in multiple reactive contexts | `createMemo` |
|
|
52
|
+
| Value used once in JSX | Inline function |
|
|
53
|
+
|
|
54
|
+
## Signals for Functions
|
|
55
|
+
|
|
56
|
+
If you need to store a function as a signal value, use the callback form of the setter to avoid Solid treating it as an updater:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const [handler, setHandler] = createSignal<() => void>(() => {});
|
|
60
|
+
|
|
61
|
+
// WRONG — Solid calls this as an updater
|
|
62
|
+
setHandler(myFunction);
|
|
63
|
+
|
|
64
|
+
// RIGHT — wrap in callback
|
|
65
|
+
setHandler(() => myFunction);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## When to Use Signals vs Stores
|
|
69
|
+
|
|
70
|
+
| Data shape | Use |
|
|
71
|
+
|---|---|
|
|
72
|
+
| Single primitive value | `createSignal` |
|
|
73
|
+
| Simple object (few properties, flat) | `createSignal` |
|
|
74
|
+
| Complex nested object | `createStore` |
|
|
75
|
+
| Array of objects with updates | `createStore` |
|
|
76
|
+
| Shared state across components | `createStore` + context |
|
|
77
|
+
|
|
78
|
+
See [Stores & Nested State](stores-and-nested-state.md) for store patterns.
|