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,166 @@
|
|
|
1
|
+
# Anti-Patterns
|
|
2
|
+
|
|
3
|
+
> Common SolidJS mistakes sourced from the [SolidJS documentation](https://docs.solidjs.com/), community guides, and [Ryan Carniato's articles](https://dev.to/ryansolid).
|
|
4
|
+
|
|
5
|
+
## 1. Destructuring Props
|
|
6
|
+
|
|
7
|
+
The single most common Solid mistake. Props use getters for reactivity — destructuring extracts the value once.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// BROKEN
|
|
11
|
+
const Card = ({ title, count }) => <div>{title}: {count}</div>;
|
|
12
|
+
|
|
13
|
+
// FIXED
|
|
14
|
+
const Card = (props) => <div>{props.title}: {props.count}</div>;
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If you need to separate props, use `splitProps`. If you need defaults, use `mergeProps`.
|
|
18
|
+
|
|
19
|
+
## 2. Forgetting to Call Signals
|
|
20
|
+
|
|
21
|
+
Signals are getter functions. Forgetting `()` passes the function itself, not the value.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// BROKEN — passes the function, not the value
|
|
25
|
+
<span>{count}</span>
|
|
26
|
+
|
|
27
|
+
// FIXED
|
|
28
|
+
<span>{count()}</span>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 3. Reading Signals Outside Reactive Context
|
|
32
|
+
|
|
33
|
+
The component body runs once. Reading a signal at the top level captures the initial value — it never updates.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// BROKEN — layout is assigned once, never reactive
|
|
37
|
+
const MyComponent = (props) => {
|
|
38
|
+
const layout = props.wide ? "grid" : "list"; // Evaluated once
|
|
39
|
+
return <div class={layout}>...</div>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// FIXED — read in JSX (reactive context)
|
|
43
|
+
const MyComponent = (props) => {
|
|
44
|
+
return <div class={props.wide ? "grid" : "list"}>...</div>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Also fixed — wrap in a function
|
|
48
|
+
const MyComponent = (props) => {
|
|
49
|
+
const layout = () => props.wide ? "grid" : "list";
|
|
50
|
+
return <div class={layout()}>...</div>;
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 4. Using Effects to Sync State
|
|
55
|
+
|
|
56
|
+
Effects that set state from other state create ordering problems and glitchy intermediate renders.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// BROKEN — effect-based synchronization
|
|
60
|
+
const [count, setCount] = createSignal(0);
|
|
61
|
+
const [doubled, setDoubled] = createSignal(0);
|
|
62
|
+
createEffect(() => setDoubled(count() * 2));
|
|
63
|
+
|
|
64
|
+
// FIXED — derivation
|
|
65
|
+
const doubled = createMemo(() => count() * 2);
|
|
66
|
+
// or: const doubled = () => count() * 2;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Rule:** If a value can be computed from reactive sources, derive it.
|
|
70
|
+
|
|
71
|
+
## 5. Using Effects for Data Fetching
|
|
72
|
+
|
|
73
|
+
Effects run after render, have no built-in loading/error states, and create race conditions on dependency changes.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// BROKEN
|
|
77
|
+
createEffect(async () => {
|
|
78
|
+
const res = await fetch(`/api/${id()}`);
|
|
79
|
+
setData(await res.json());
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// FIXED
|
|
83
|
+
const [data] = createResource(id, fetchData);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`createResource` integrates with Suspense, handles loading/error, and manages race conditions.
|
|
87
|
+
|
|
88
|
+
## 6. Using `.map()` Instead of `<For>`
|
|
89
|
+
|
|
90
|
+
JavaScript `.map()` recreates all DOM nodes whenever the array signal changes. `<For>` tracks items by reference and only updates what changed.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// INEFFICIENT — full DOM rebuild on any array change
|
|
94
|
+
<ul>{items().map(item => <li>{item.name}</li>)}</ul>
|
|
95
|
+
|
|
96
|
+
// CORRECT — granular updates
|
|
97
|
+
<ul><For each={items()}>{(item) => <li>{item.name}</li>}</For></ul>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 7. Using `&&` for Conditional Rendering
|
|
101
|
+
|
|
102
|
+
JavaScript short-circuit evaluation doesn't give Solid the boundaries it needs to optimize.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// SUBOPTIMAL — no Solid optimization
|
|
106
|
+
{isVisible() && <Modal />}
|
|
107
|
+
|
|
108
|
+
// CORRECT — Solid can optimize the boundary
|
|
109
|
+
<Show when={isVisible()}><Modal /></Show>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 8. Mutating Store State Directly
|
|
113
|
+
|
|
114
|
+
Stores use proxies. Direct mutation bypasses the reactive system.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// BROKEN — Solid doesn't see this change
|
|
118
|
+
state.tasks[0].completed = true;
|
|
119
|
+
|
|
120
|
+
// FIXED — go through setState
|
|
121
|
+
setState("tasks", 0, "completed", true);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 9. Creating Unnecessary Reactivity
|
|
125
|
+
|
|
126
|
+
Not everything needs to be a signal. Static configuration, constants, and values that never change don't benefit from reactivity.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// UNNECESSARY
|
|
130
|
+
const [apiUrl] = createSignal("https://api.example.com");
|
|
131
|
+
|
|
132
|
+
// JUST USE A CONSTANT
|
|
133
|
+
const API_URL = "https://api.example.com";
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## 10. Missing `onCleanup`
|
|
137
|
+
|
|
138
|
+
Effects that create subscriptions, timers, or event listeners without cleanup cause memory leaks.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// LEAKS — interval is never cleared
|
|
142
|
+
createEffect(() => {
|
|
143
|
+
setInterval(() => tick(), 1000);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// FIXED
|
|
147
|
+
createEffect(() => {
|
|
148
|
+
const id = setInterval(() => tick(), 1000);
|
|
149
|
+
onCleanup(() => clearInterval(id));
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Quick Reference
|
|
154
|
+
|
|
155
|
+
| Mistake | Fix |
|
|
156
|
+
|---|---|
|
|
157
|
+
| Destructure props | Use `props.x` directly |
|
|
158
|
+
| `count` without `()` | Always call signal getters |
|
|
159
|
+
| Signal read at top level | Read in JSX or wrap in `() =>` |
|
|
160
|
+
| Effect sets state from state | Use `createMemo` |
|
|
161
|
+
| Effect fetches data | Use `createResource` |
|
|
162
|
+
| `.map()` for lists | Use `<For>` |
|
|
163
|
+
| `&&` for conditionals | Use `<Show>` |
|
|
164
|
+
| Direct store mutation | Use `setState()` |
|
|
165
|
+
| Signal for constants | Use a plain variable |
|
|
166
|
+
| No `onCleanup` | Always clean up side effects |
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Component Patterns
|
|
2
|
+
|
|
3
|
+
> Sources: [TypeScript](https://docs.solidjs.com/configuration/typescript), [Props](https://docs.solidjs.com/concepts/components/props) — SolidJS Docs
|
|
4
|
+
|
|
5
|
+
## Component Type Hierarchy
|
|
6
|
+
|
|
7
|
+
SolidJS provides four component types for TypeScript:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import type { Component, ParentComponent, FlowComponent, VoidComponent } from "solid-js";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Type | Children | Use Case |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `Component<P>` | No opinion | Base type, generic components |
|
|
16
|
+
| `ParentComponent<P>` | Optional `JSX.Element` | Components that accept children |
|
|
17
|
+
| `FlowComponent<P, C>` | Required, typed | Control flow (`Show`, `For`-like) |
|
|
18
|
+
| `VoidComponent<P>` | Forbidden | Leaf components (icons, inputs) |
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
const Card: ParentComponent<{ title: string }> = (props) => {
|
|
22
|
+
return (
|
|
23
|
+
<div class="card">
|
|
24
|
+
<h2>{props.title}</h2>
|
|
25
|
+
{props.children}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const Icon: VoidComponent<{ name: string }> = (props) => {
|
|
31
|
+
return <svg class={`icon-${props.name}`} />;
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Generic Components
|
|
36
|
+
|
|
37
|
+
The `Component` types cannot be used for generic components. Use function declarations instead:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
function List<T>(props: { items: T[]; render: (item: T) => JSX.Element }): JSX.Element {
|
|
41
|
+
return <For each={props.items}>{props.render}</For>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Usage — T is inferred
|
|
45
|
+
<List items={users()} render={(user) => <span>{user.name}</span>} />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Refs
|
|
49
|
+
|
|
50
|
+
Refs may be `undefined` before mount. Always account for this:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
const MyComponent: VoidComponent = () => {
|
|
54
|
+
let inputRef: HTMLInputElement | undefined;
|
|
55
|
+
|
|
56
|
+
onMount(() => {
|
|
57
|
+
inputRef?.focus(); // Safe access after mount
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return <input ref={inputRef} />;
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Alternatively, use the definite assignment assertion in JSX: `ref={inputRef!}`.
|
|
65
|
+
|
|
66
|
+
## Composition Over Inheritance
|
|
67
|
+
|
|
68
|
+
Solid components are plain functions — compose them by passing components as props or using children:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const Layout: ParentComponent<{ sidebar: JSX.Element }> = (props) => {
|
|
72
|
+
return (
|
|
73
|
+
<div class="layout">
|
|
74
|
+
<aside>{props.sidebar}</aside>
|
|
75
|
+
<main>{props.children}</main>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
<Layout sidebar={<Navigation />}>
|
|
81
|
+
<PageContent />
|
|
82
|
+
</Layout>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Event Handling
|
|
86
|
+
|
|
87
|
+
Inline event handlers get automatic type inference. For extracted handlers, use `JSX.EventHandler`:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const onInput: JSX.EventHandler<HTMLInputElement, InputEvent> = (event) => {
|
|
91
|
+
// event.currentTarget is HTMLInputElement
|
|
92
|
+
// event.target may be any element within
|
|
93
|
+
console.log(event.currentTarget.value);
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Custom Events
|
|
98
|
+
|
|
99
|
+
Extend the JSX namespace:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
declare module "solid-js" {
|
|
103
|
+
namespace JSX {
|
|
104
|
+
interface CustomEvents {
|
|
105
|
+
customClick: CustomEvent<{ id: string }>;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Custom Directives
|
|
112
|
+
|
|
113
|
+
Register with `use:` prefix. Declare via the JSX namespace:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
declare module "solid-js" {
|
|
117
|
+
namespace JSX {
|
|
118
|
+
interface Directives {
|
|
119
|
+
tooltip: string;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function tooltip(el: HTMLElement, accessor: () => string) {
|
|
125
|
+
// Set up tooltip on el using accessor()
|
|
126
|
+
onCleanup(() => { /* teardown */ });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Usage
|
|
130
|
+
<div use:tooltip={"Hello!"} />
|
|
131
|
+
```
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
|
@@ -0,0 +1,124 @@
|
|
|
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>` |
|