solid-js 2.0.0-beta.0 → 2.0.0-beta.10

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.
Files changed (38) hide show
  1. package/CHEATSHEET.md +640 -0
  2. package/README.md +42 -188
  3. package/dist/dev.cjs +451 -283
  4. package/dist/dev.js +428 -286
  5. package/dist/server.cjs +735 -279
  6. package/dist/server.js +715 -282
  7. package/dist/solid.cjs +443 -255
  8. package/dist/solid.js +420 -258
  9. package/package.json +67 -39
  10. package/types/client/component.d.ts +65 -19
  11. package/types/client/core.d.ts +110 -34
  12. package/types/client/flow.d.ts +176 -42
  13. package/types/client/hydration.d.ts +525 -31
  14. package/types/index.d.ts +9 -12
  15. package/types/server/component.d.ts +12 -11
  16. package/types/server/core.d.ts +19 -14
  17. package/types/server/flow.d.ts +76 -21
  18. package/types/server/hydration.d.ts +49 -7
  19. package/types/server/index.d.ts +5 -7
  20. package/types/server/shared.d.ts +9 -4
  21. package/types/server/signals.d.ts +45 -18
  22. package/types/types.d.ts +15 -0
  23. package/types-cjs/client/component.d.cts +120 -0
  24. package/types-cjs/client/core.d.cts +141 -0
  25. package/types-cjs/client/flow.d.cts +234 -0
  26. package/types-cjs/client/hydration.d.cts +570 -0
  27. package/types-cjs/index.d.cts +17 -0
  28. package/types-cjs/package.json +3 -0
  29. package/types-cjs/server/component.d.cts +67 -0
  30. package/types-cjs/server/core.d.cts +49 -0
  31. package/types-cjs/server/flow.d.cts +115 -0
  32. package/types-cjs/server/hydration.d.cts +63 -0
  33. package/types-cjs/server/index.d.cts +10 -0
  34. package/types-cjs/server/shared.d.cts +50 -0
  35. package/types-cjs/server/signals.d.cts +87 -0
  36. package/types-cjs/types.d.cts +15 -0
  37. package/jsx-runtime.d.ts +0 -1
  38. package/types/jsx.d.ts +0 -4129
package/CHEATSHEET.md ADDED
@@ -0,0 +1,640 @@
1
+ # Solid 2.0 — Cheatsheet
2
+
3
+ One-page reference for Solid 2.0. Every API exists in `solid-js` unless noted. DOM APIs are in `@solidjs/web`.
4
+
5
+ > **AI codegen warning.** Solid is **not React**, and 2.0 is **not 1.x**. Both priors are dominant bug sources here — distrust pattern-matching from either. The bottom of this file lists 2.0-specific corrections; read them before generating code.
6
+
7
+ ---
8
+
9
+ ## Imports
10
+
11
+ ```ts
12
+ import {
13
+ createSignal,
14
+ createMemo,
15
+ createEffect,
16
+ createRoot,
17
+ For,
18
+ Show,
19
+ Switch,
20
+ Match,
21
+ Loading,
22
+ Errored,
23
+ Repeat,
24
+ Reveal,
25
+ createStore,
26
+ createProjection,
27
+ snapshot,
28
+ reconcile,
29
+ merge,
30
+ omit,
31
+ action,
32
+ createOptimistic,
33
+ createOptimisticStore,
34
+ isPending,
35
+ latest,
36
+ refresh,
37
+ untrack,
38
+ flush,
39
+ onSettled,
40
+ createContext,
41
+ useContext,
42
+ children,
43
+ lazy,
44
+ createUniqueId
45
+ } from "solid-js";
46
+
47
+ import { render, hydrate, Portal, dynamic, Dynamic } from "@solidjs/web";
48
+ ```
49
+
50
+ Web projects should configure TypeScript with `"jsxImportSource": "@solidjs/web"`.
51
+ `solid-js` no longer provides `solid-js/jsx-runtime`; renderer packages own JSX types.
52
+
53
+ Old subpaths are gone:
54
+ `solid-js/web` → `@solidjs/web`. `solid-js/store` → `solid-js`. `solid-js/h` → `@solidjs/h`. `solid-js/html` → `@solidjs/html`. `solid-js/universal` → `@solidjs/universal`.
55
+
56
+ ---
57
+
58
+ ## Signals & memos
59
+
60
+ ```ts
61
+ // Plain signal
62
+ const [count, setCount] = createSignal(0);
63
+ count(); // read (call it!)
64
+ setCount(1); // queues; read returns last committed until flush
65
+ setCount(c => c + 1); // updater form
66
+
67
+ // Readonly derived
68
+ const doubled = createMemo(() => count() * 2);
69
+ doubled();
70
+
71
+ // Writable derived ("writable memo")
72
+ const [value, setValue] = createSignal(() => props.initial);
73
+
74
+ // Options
75
+ createSignal(0, { ownedWrite: true }); // allow writes from inside owned scope
76
+ createSignal(0, { unobserved: () => cleanup() }); // fires when no subscribers
77
+ createMemo(fn, { lazy: true }); // defer first compute until read; autodispose when unobserved
78
+ createMemo(fn, { equals: (a, b) => a.id === b.id });
79
+ ```
80
+
81
+ **Reads update only after flush.** `setX(v); x()` returns the _previous_ value until the next microtask or `flush()`.
82
+
83
+ ```ts
84
+ setCount(1);
85
+ count(); // still 0
86
+ flush();
87
+ count(); // 1
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Effects
93
+
94
+ ```ts
95
+ // Two-arg form is the only form. Compute tracks; apply runs side effects.
96
+ createEffect(
97
+ () => count(), // compute (tracks)
98
+ (value, prev) => {
99
+ // apply (untracked)
100
+ el.title = value;
101
+ return () => {
102
+ /* cleanup */
103
+ }; // optional cleanup
104
+ }
105
+ );
106
+
107
+ // With error handling
108
+ createEffect(() => fetchData(id()), {
109
+ effect: data => render(data),
110
+ error: (err, cleanup) => console.error(err)
111
+ });
112
+
113
+ // Run on next change only (skip initial)
114
+ createEffect(
115
+ () => count(),
116
+ v => log(v),
117
+ { defer: true }
118
+ );
119
+
120
+ // Schedule once after the current activity settles — the canonical
121
+ // "do this once and clean it up on dispose" primitive (replaces 1.x
122
+ // onMount + onCleanup for component-level lifecycle).
123
+ onSettled(() => {
124
+ const id = setInterval(tick, 1000);
125
+ return () => clearInterval(id);
126
+ });
127
+ ```
128
+
129
+ `onSettled` works in component bodies (after first reactive settle) **and** in event handlers (defer work until the triggered transition settles). For component-level setup-and-teardown, **use `onSettled` with a returned cleanup, not `onCleanup` directly** — `onCleanup` is reserved for reactive cleanup inside computations (see Advanced).
130
+
131
+ ---
132
+
133
+ ## Reactive control utilities
134
+
135
+ ```ts
136
+ untrack(() => count()); // read without subscribing
137
+ flush(); // drain queued updates synchronously
138
+ isEqual(a, b); // default equality
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Stores
144
+
145
+ ```ts
146
+ const [store, setStore] = createStore({ user: { name: "A" }, list: [] });
147
+
148
+ // Draft-first setter (canonical)
149
+ setStore(s => {
150
+ s.user.name = "B";
151
+ s.list.push("x");
152
+ });
153
+
154
+ // Return a value when mutation is awkward (filter/remove most often).
155
+ // Arrays replace by index + length; objects shallow-diff at the top level.
156
+ // No keyed reconciliation here — for that, use createProjection / createStore(fn).
157
+ setStore(s => s.list.filter(x => x !== "x"));
158
+ setStore(s => ({ ...s, list: [] }));
159
+
160
+ // Reconcile new data into a sub-tree (preserve identity)
161
+ setStore(s => {
162
+ reconcile(serverTodos, "id")(s.todos);
163
+ });
164
+
165
+ // Derived stores (mirror signal/memo split)
166
+ const items = createProjection(async () => api.list(), [], { key: "id" }); // readonly
167
+ const [cache, setCache] = createStore(
168
+ draft => {
169
+ draft.x = compute();
170
+ },
171
+ { x: 0 }
172
+ ); // writable
173
+ ```
174
+
175
+ `undefined` is a real value in `merge` / setters — it overrides, not "skip".
176
+
177
+ ---
178
+
179
+ ## Props
180
+
181
+ **Props are reactive values, not accessors.** Two rules, one underlying model — and together the most common AI-generated bug class in Solid.
182
+
183
+ ```jsx
184
+ const [count, setCount] = createSignal(0);
185
+
186
+ // 1. At the call site: pass the VALUE. Call accessors at the JSX boundary.
187
+ <Counter value={count()} /> // ✅
188
+ <Counter value={count} /> // ❌ child receives a function, not a number
189
+
190
+ // 2. In the child: read via `props.x`. The *property access* is what tracks.
191
+ function Counter(props) {
192
+ return <div>{props.value}</div>; // ✅ re-reads on each render
193
+ }
194
+ function Counter({ value }) { // ❌ destructure unwraps once, reactivity is dead
195
+ return <div>{value}</div>;
196
+ }
197
+ ```
198
+
199
+ The rules are two sides of the same boundary: the parent collapses its accessors to values when handing off; the JSX runtime re-wraps `props` so that `props.value` re-reads on each access. Skip step 1 and the child gets a function; skip step 2 and the child reads once.
200
+
201
+ If you genuinely need to forward a getter (rare — render props, lazy slots), pass `getValue={() => count()}` and document it. Default to values.
202
+
203
+ ### Helpers
204
+
205
+ ```ts
206
+ const merged = merge(defaults, props, overrides); // replaces mergeProps
207
+ const rest = omit(props, "class", "style"); // replaces splitProps
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Async
213
+
214
+ ```ts
215
+ // Async is just "any computation that returns a Promise / AsyncIterable"
216
+ const user = createMemo(() => fetchUser(id()));
217
+ // Reading user() suspends until ready; wrap in <Loading>.
218
+
219
+ // "Refreshing…" indicator (false during initial Loading)
220
+ isPending(() => user());
221
+
222
+ // Peek at the in-flight value during a transition
223
+ latest(id);
224
+
225
+ // Force recompute of a derived read after a server write
226
+ refresh(user);
227
+ refresh(() => query.user(id()));
228
+ ```
229
+
230
+ ---
231
+
232
+ ## Actions & optimistic
233
+
234
+ ```ts
235
+ const [todos, setOptimisticTodos] = createOptimisticStore(() => api.list(), []);
236
+
237
+ const addTodo = action(function* (todo) {
238
+ setOptimisticTodos(s => {
239
+ s.push(todo);
240
+ }); // optimistic write
241
+ yield api.add(todo); // async work
242
+ refresh(todos); // re-derive
243
+ });
244
+
245
+ // Optimistic signal
246
+ const [name, setName] = createOptimistic("Alice");
247
+ ```
248
+
249
+ Optimistic writes revert when the transition completes.
250
+
251
+ ---
252
+
253
+ ## Control-flow components
254
+
255
+ ```tsx
256
+ // List, keyed by identity (default)
257
+ <For each={items()}>
258
+ {(item, i) => <Row item={item()} index={i()} />}
259
+ </For>
260
+
261
+ // List, non-keyed (replaces <Index>)
262
+ <For each={items()} keyed={false}>
263
+ {(item, i) => <Row item={item()} index={i()} />}
264
+ </For>
265
+
266
+ // List with custom key
267
+ <For each={items()} keyed={t => t.id} fallback={<Empty />}>
268
+ {item => <Row todo={item()} />}
269
+ </For>
270
+
271
+ // Range / count (no diffing) — i is a plain number, not an accessor
272
+ <Repeat count={store.items.length} fallback={<Empty />}>
273
+ {i => <Row name={store.items[i].name} />}
274
+ </Repeat>
275
+
276
+ // Conditional (function child receives narrowed accessor — call it!)
277
+ <Show when={user()} fallback={<Login />}>
278
+ {u => <Profile user={u()} />}
279
+ </Show>
280
+
281
+ // Branching
282
+ <Switch fallback={<NotFound />}>
283
+ <Match when={route() === "home"}><Home /></Match>
284
+ <Match when={route() === "profile"}>{() => <Profile />}</Match>
285
+ </Switch>
286
+
287
+ // Async boundary (replaces <Suspense>)
288
+ <Loading fallback={<Spinner />} on={id()}>
289
+ <Profile />
290
+ </Loading>
291
+
292
+ // Error boundary (replaces <ErrorBoundary>)
293
+ <Errored fallback={(err, reset) => <button onClick={reset}>retry</button>}>
294
+ <Page />
295
+ </Errored>
296
+
297
+ // Coordinate sibling Loadings (replaces <SuspenseList>).
298
+ // Default order is "sequential" — siblings reveal in registration order
299
+ // as each resolves. `collapsed` (sequential only) suppresses tail-sibling
300
+ // fallbacks past the frontier. Other orders: "together" | "natural".
301
+ <Reveal collapsed>
302
+ <Loading fallback={<S/>}><A/></Loading>
303
+ <Loading fallback={<S/>}><B/></Loading>
304
+ </Reveal>
305
+
306
+ // Dynamic component — factory form (canonical). Stable Component identity;
307
+ // composes with lazy(), routing, polymorphic patterns.
308
+ import { dynamic } from "@solidjs/web";
309
+
310
+ const Active = dynamic(() => isEditing() ? Editor : Viewer);
311
+ return <Active value={value()} />;
312
+
313
+ // JSX-wrapper convenience form. Use when the source is inline and you don't
314
+ // need to capture the component identity.
315
+ import { Dynamic } from "@solidjs/web";
316
+ <Dynamic component={isEditing() ? Editor : Viewer} value={value()} />
317
+ ```
318
+
319
+ `<For>` non-keyed: `item` and `i` are **accessors** — call them: `item()`, `i()`.
320
+ `<Repeat>`: `i` is a **plain number**.
321
+
322
+ ---
323
+
324
+ ## Context, components, lazy
325
+
326
+ Context is for state scoped to a subtree of the component tree. **If you
327
+ want truly app-wide state, don't use Context — a module-scope signal/store
328
+ _is_ a global.** That's why the default-less form requires a Provider.
329
+
330
+ ```tsx
331
+ // Default-less — the canonical form. No Provider → ContextNotFoundError.
332
+ type TodosCtx = readonly [Store<Todo[]>, TodoActions];
333
+ const TodosContext = createContext<TodosCtx>();
334
+
335
+ function App() {
336
+ return (
337
+ <TodosContext value={createTodos()}>
338
+ {" "}
339
+ {/* the context IS the provider */}
340
+ <TodoList />
341
+ </TodosContext>
342
+ );
343
+ }
344
+
345
+ function TodoList() {
346
+ const [todos, { addTodo }] = useContext(TodosContext); // typed as TodosCtx
347
+ // ...
348
+ }
349
+ ```
350
+
351
+ ```tsx
352
+ // Default form — only for primitive fallbacks (theme, locale, frozen config).
353
+ // Outside any Provider, useContext returns the default.
354
+ const Theme = createContext<"light" | "dark">("light");
355
+
356
+ function Page() {
357
+ const theme = useContext(Theme); // "light" | "dark"
358
+ return <div class={theme}>...</div>;
359
+ }
360
+ ```
361
+
362
+ Don't write a `useTodos`-style wrapper that re-throws on missing Provider —
363
+ the default-less form already throws, and `useContext` is typed `T` (no
364
+ `| undefined`). The wrapper is React-flavored boilerplate that no longer
365
+ earns its keep.
366
+
367
+ ```tsx
368
+ // Resolve children once
369
+ const list = children(() => props.children);
370
+ list.toArray();
371
+
372
+ // Async component
373
+ const Heavy = lazy(() => import("./Heavy"));
374
+ ```
375
+
376
+ Component types:
377
+
378
+ ```ts
379
+ type Basic = Component<P>; // no implicit children
380
+ type Empty = VoidComponent<P>; // forbids children
381
+ type WithChildren = ParentComponent<P>; // optional renderer-neutral Element children
382
+ type Flow = FlowComponent<P, C>; // requires children of type C
383
+ ```
384
+
385
+ ---
386
+
387
+ ## DOM rendering
388
+
389
+ ```ts
390
+ import { render, hydrate, Portal } from "@solidjs/web";
391
+
392
+ const dispose = render(() => <App />, document.getElementById("root")!);
393
+ hydrate(() => <App />, document.getElementById("root")!);
394
+
395
+ <Portal mount={document.body}><Modal /></Portal>
396
+ ```
397
+
398
+ ### Refs and directives
399
+
400
+ ```jsx
401
+ // Element access
402
+ <button ref={el => (myButton = el)} />
403
+
404
+ // Directive factory (replaces use:)
405
+ <input ref={autofocus} />
406
+ <button ref={tooltip({ content: "Save" })} />
407
+
408
+ // Compose multiple
409
+ <button ref={[autofocus, tooltip({ content: "Save" })]} />
410
+ ```
411
+
412
+ Two-phase directive (recommended):
413
+
414
+ ```ts
415
+ function titleDirective(source) {
416
+ // Setup phase (owned): create primitives.
417
+ let el;
418
+ createEffect(source, value => {
419
+ if (el) el.title = value;
420
+ });
421
+ // Apply phase (unowned): DOM writes only.
422
+ return nextEl => {
423
+ el = nextEl;
424
+ el.title = source();
425
+ };
426
+ }
427
+ ```
428
+
429
+ ### Attributes
430
+
431
+ ```jsx
432
+ <video muted={true} /> // boolean = presence/absence
433
+ <video muted={false} />
434
+ <some-element enabled="true" /> // when platform requires the string
435
+ ```
436
+
437
+ Lowercase HTML attribute names. No `attr:` / `bool:` / `oncapture:` namespaces. Event handlers stay camelCase (`onClick`).
438
+
439
+ ### Conditional classes — always use the array/object form
440
+
441
+ ```jsx
442
+ <div class="card" /> // static string
443
+ <div class={{ active: isActive(), invalid: !valid() }} /> // object: toggle by truthiness
444
+ <div class={["card", props.class, { active: isActive() }]} /> // array: merge entries
445
+ ```
446
+
447
+ Array entries are always-on (or further nested arrays/objects). Object entries toggle by truthiness. There is no `classList` prop — the array+object form replaces it.
448
+
449
+ **Don't build class strings manually.** String concatenation, template literals, and `.join(" ")` over conditionals are the React/`classnames` reflex. Use the array+object form so conditions compose:
450
+
451
+ ```jsx
452
+ <li class={["todo", { completed: props.todo.completed, errored: !!err() }]} />; // ✅
453
+
454
+ const cls = [
455
+ "todo", // ❌
456
+ props.todo.completed && "completed",
457
+ err() && "errored"
458
+ ]
459
+ .filter(Boolean)
460
+ .join(" ");
461
+ return <li class={cls} />;
462
+ ```
463
+
464
+ ---
465
+
466
+ ## SSR (server entry)
467
+
468
+ ```ts
469
+ import { renderToString, renderToStringAsync, renderToStream, isServer, isDev } from "@solidjs/web";
470
+ ```
471
+
472
+ `Portal` throws on the server. `Reveal` `order="together"` and `collapsed` require streaming (`renderToStream` / `renderToStringAsync`).
473
+
474
+ ---
475
+
476
+ ## Diagnostics (dev mode)
477
+
478
+ Common dev-mode warnings/errors you may hit:
479
+
480
+ - **Top-level reactive read in component body** — read inside JSX or wrap in `untrack`/`createMemo`.
481
+ - **Write under owned scope** — move setters into event handlers / `onSettled` / `untrack`, or opt in with `{ ownedWrite: true }`.
482
+ - **Strict read untracked** — extract values in the compute phase; don't read store proxies inside the effect callback.
483
+ - **Multiple Solid instances** — single `solid-js` install required.
484
+
485
+ Each diagnostic has a code (see RFC 08 / runtime error message) — search the docs by code.
486
+
487
+ ---
488
+
489
+ ## Advanced / escape hatches
490
+
491
+ Reach for these only when the named situation applies. **If you're not sure, you don't need them** — the common-path APIs above are the answer.
492
+
493
+ ```ts
494
+ // Reactive cleanup inside a computation — runs before the next compute and
495
+ // on disposal. For component-level setup-and-teardown, use onSettled and
496
+ // return a cleanup; onCleanup is for library/primitive internals where the
497
+ // cleanup is tied to a reactive run, not a component lifecycle.
498
+ onCleanup(() => disposeReactiveResource());
499
+
500
+ // Deep tracking — only when an effect needs to react to *any* nested store change.
501
+ // Default store tracking is property-level (preferred).
502
+ createEffect(
503
+ () => deep(store),
504
+ snap => save(snap)
505
+ );
506
+
507
+ // Plain (non-reactive) deep copy of a store. For serialization (JSON.stringify,
508
+ // localStorage, sending over the wire) and tests that need a plain-object
509
+ // assertion target. Inside reactive scopes, prefer reading individual values.
510
+ JSON.stringify(snapshot(store));
511
+
512
+ // Render-phase synchronous effect — for DOM bindings that must run during render
513
+ // (the runtime's own attribute/property bindings). For app code, use createEffect.
514
+ createRenderEffect(
515
+ () => props.title,
516
+ v => {
517
+ el.title = v;
518
+ }
519
+ );
520
+
521
+ // Single-callback effect that may re-run in async situations.
522
+ // Rare; prefer createEffect.
523
+ createTrackedEffect(() => log(count()));
524
+
525
+ // One-shot tracked callback (advanced reactive patterns).
526
+ const track = createReaction(() => doWork());
527
+ track(() => count());
528
+
529
+ // Manually create an owned reactive scope. App code uses render(); reach for
530
+ // createRoot in tests, library setup, and other non-render entry points where
531
+ // you need an owner so reactive primitives can dispose properly.
532
+ createRoot(dispose => {
533
+ const [count, setCount] = createSignal(0);
534
+ // ...
535
+ dispose();
536
+ });
537
+
538
+ // Detach a root from its parent (module singletons, external integrations only).
539
+ runWithOwner(null, () => {
540
+ /* ... */
541
+ });
542
+
543
+ // Get the current owner. Mostly used to capture and restore an owner across an
544
+ // async boundary inside library code.
545
+ const owner = getOwner();
546
+ runWithOwner(owner, () => {
547
+ /* ... */
548
+ });
549
+
550
+ // Wait for a reactive expression to settle (imperative code / tests).
551
+ const v = await resolve(() => user());
552
+
553
+ // Are we inside a refresh() cycle? Almost never needed in app code.
554
+ isRefreshing();
555
+
556
+ // Throw to signal "not ready" through the reactive graph (library authors).
557
+ throw new NotReadyError();
558
+
559
+ // 1.x-style path setter compat. Use only when migrating; draft-first is canonical.
560
+ setStore(storePath("user", "address", "city", "Paris"));
561
+ ```
562
+
563
+ ---
564
+
565
+ ## What changed from 1.x (the AI footgun list)
566
+
567
+ If your training data is 1.x, these are the corrections. **Read this before generating Solid 2.0 code.**
568
+
569
+ ### Imports moved
570
+
571
+ - `solid-js/web` → `@solidjs/web`
572
+ - `solid-js/store` → `solid-js` (store APIs moved into core)
573
+ - `solid-js/h` / `solid-js/html` / `solid-js/universal` → `@solidjs/h` / `@solidjs/html` / `@solidjs/universal`
574
+ - `jsxImportSource: "solid-js"` → `"@solidjs/web"` for web JSX (`@solidjs/h` for hyperscript JSX)
575
+
576
+ ### Renames
577
+
578
+ | 1.x | 2.0 |
579
+ | ------------------- | --------------------------------------------------------- |
580
+ | `Suspense` | `Loading` |
581
+ | `SuspenseList` | `Reveal` |
582
+ | `ErrorBoundary` | `Errored` |
583
+ | `mergeProps` | `merge` |
584
+ | `splitProps` | `omit` |
585
+ | `unwrap` | `snapshot` |
586
+ | `onMount` | `onSettled` |
587
+ | `createSelector` | `createProjection` (or `createStore(fn)`) |
588
+ | `equalFn` | `isEqual` |
589
+ | `getListener` | `getObserver` |
590
+ | `Context.Provider` | `<Context value={...}>` (context value _is_ the provider) |
591
+ | `classList={{...}}` | `class={{...}}` (object/array forms) |
592
+
593
+ ### Removed (with replacements)
594
+
595
+ | Removed | Use instead |
596
+ | ---------------------------------- | ------------------------------------------------------------------- |
597
+ | `batch` | Default microtask batching; `flush()` to apply now |
598
+ | `createComputed` | `createMemo` / split `createEffect` / function-form `createSignal` |
599
+ | `createResource` | Async computations + `<Loading>` (`createMemo(() => fetchX(id()))`) |
600
+ | `startTransition`, `useTransition` | Built-in transitions; `isPending` / `<Loading>` / optimistic APIs |
601
+ | `on(...)` helper | Split effects (compute phase = explicit deps) |
602
+ | `onError` / `catchError` | `<Errored>` or effect `error` option |
603
+ | `produce` | Default — store setters are draft-first |
604
+ | `createMutable` / `modifyMutable` | `createStore` with draft setters |
605
+ | `from` / `observable` | Async iterables in computations / `createEffect` to push out |
606
+ | `Index` | `<For keyed={false}>` |
607
+ | `indexArray` | `mapArray` (handles non-keyed too) |
608
+ | `use:foo={x}` directives | `ref={foo(x)}` (or array `ref={[a, b(x)]}`) |
609
+ | `attr:` / `bool:` namespaces | Standard attribute behavior |
610
+ | `oncapture:` | `addEventListener(..., { capture: true })` |
611
+ | `resetErrorBoundaries` | Boundaries heal automatically |
612
+
613
+ ### Behavior changes
614
+
615
+ - **`createEffect` takes two arguments now**: `(compute, apply)`. The single-arg form is gone — using it is an error.
616
+ - **Setters don't update reads immediately** — values become visible after the microtask flushes (or via `flush()`).
617
+ - **No writes inside owned scope** — writing a signal/store from inside a memo, effect compute, or component body throws in dev. Move writes to event handlers, `onSettled`, or untracked blocks. Opt in narrowly with `{ ownedWrite: true }` for internal state.
618
+ - **No top-level reactive reads in component body** — reading signals/props directly at the top of a component warns. Read inside JSX, a memo, or `untrack`.
619
+ - **Props are values, not accessors** — at the call site call accessors (`<X v={count()} />`, not `<X v={count} />`). The single most common AI-generated bug.
620
+ - **Don't destructure props** — `function Comp({ name })` warns; use `props.name` to keep reactivity. (Same root cause as above; see the Props section.)
621
+ - **`<For>` non-keyed children are accessors** — `(item, i) => ...` where `item` and `i` are functions. Call them: `item()`, `i()`.
622
+ - **`<Show>` / `<Match>` function children receive narrowed accessors** — also call them.
623
+ - **Stores: setters take a draft callback** — mutate the draft in place by default. Returning a new value is shallow (array index-replace, object top-level diff); reach for it for filter/remove. Keyed reconcile is a _projection-fn_ feature, not a setter feature.
624
+ - **`undefined` is a real value in `merge`** — it overrides rather than "skip this key".
625
+ - **Async lives in computations** — return a Promise/AsyncIterable from `createMemo`/`createStore(fn)`/`createProjection`. Reads suspend; wrap in `<Loading>`.
626
+ - **`Loading` is initial-only by default** — once content has rendered, revalidation keeps it visible. Use `isPending(() => x())` for "refreshing…" indicators. Use `<Loading on={key}>` to re-show fallback on key changes.
627
+ - **No `Suspense.Provider` or single error path** — async errors flow to `<Errored>` (or effect `error`); no inline `resource.error` branching.
628
+ - **`createRoot` is owned by parent by default** — disposed when parent disposes. To detach: `runWithOwner(null, fn)`.
629
+ - **Refs are functions** — `ref={el => ...}`. No `useRef`-style ref objects. Compose with arrays: `ref={[a, b]}`.
630
+ - **Boolean attributes are presence/absence** — `<video muted={false} />` removes the attribute.
631
+ - **Built-in attributes are lowercase** — `tabindex` not `tabIndex`. Event handlers stay camelCase (`onClick`).
632
+ - **In tests, `flush()` before asserting on signals** — `setCount(1); flush(); expect(count()).toBe(1)`.
633
+ - **Reactive primitives need an owner** — wrap test code in `createRoot(dispose => { ... })` or you'll leak.
634
+
635
+ ---
636
+
637
+ ## See also
638
+
639
+ - [`MIGRATION.md`](https://github.com/solidjs/solid/blob/main/documentation/solid-2.0/MIGRATION.md) — full beta-tester migration guide.
640
+ - [Solid 2.0 RFCs](https://github.com/solidjs/solid/tree/main/documentation/solid-2.0) — deep-dive design docs by subsystem.