diff --git a/src/routes/(6)migration/(5)from-react.mdx b/src/routes/(6)migration/(5)from-react.mdx
index c2abfe3ff..a5917dab3 100644
--- a/src/routes/(6)migration/(5)from-react.mdx
+++ b/src/routes/(6)migration/(5)from-react.mdx
@@ -76,12 +76,16 @@ Solid primitives are regular function calls and do not use hook ordering or nami
Translate each hook according to the role it serves:
-- Local scalar state usually maps to [`createSignal`](/reference/solid-js/reactivity/create-signal).
-- Object or collection state can map to [`createStore`](/reference/solid-js/stores/create-store).
-- Cached derived state can map to [`createMemo`](/reference/solid-js/reactivity/create-memo).
-- Imperative synchronization can map to [`createEffect`](/reference/solid-js/reactivity/create-effect).
+- Local scalar state (`useState`) usually maps to [`createSignal`](/reference/solid-js/reactivity/create-signal).
+- Object or collection state, including state managed by `useReducer`, can map to [`createStore`](/reference/solid-js/stores/create-store); see [Replace a reducer with store functions](#replace-a-reducer-with-store-functions).
+- Cached derived state (`useMemo`) can map to [`createMemo`](/reference/solid-js/reactivity/create-memo).
+ `useCallback` and `React.memo` have no counterpart; see [Remove what has no Solid counterpart](#remove-what-has-no-solid-counterpart).
+- Imperative synchronization (`useEffect`, `useLayoutEffect`) can map to [`createEffect`](/reference/solid-js/reactivity/create-effect), and setup that runs once on mount maps to [`onSettled`](/reference/solid-js/lifecycle-actions/on-settled); see [Run setup once on mount](#run-setup-once-on-mount).
- Shared subtree state can map to [`createContext`](/reference/solid-js/components-context/create-context) and [`useContext`](/reference/solid-js/components-context/use-context).
-- Reusable stateful behavior can become a custom primitive: a plain function that creates and returns Solid primitives.
+- A store outside the framework, read with `useSyncExternalStore`, maps to a signal written from its subscription; see [Read an external store](#read-an-external-store).
+- Pending UI from `useTransition` and `useDeferredValue` maps to [`isPending`](/reference/solid-js/reactivity/is-pending), [`latest`](/reference/solid-js/reactivity/latest), and [`createDeferred`](/reference/solid-js/reactivity/create-deferred); see [Show pending work](#show-pending-work).
+- Optimistic mutations with `useOptimistic` map to [`action`](/reference/solid-js/lifecycle-actions/action) and the optimistic primitives; see [Write data with actions](#write-data-with-actions).
+- Reusable stateful behavior (a custom hook) can become a custom primitive: a plain function that creates and returns Solid primitives; see [Turn a custom hook into a custom primitive](#turn-a-custom-hook-into-a-custom-primitive).
These mappings describe related responsibilities rather than interchangeable implementations.
Choose the Solid primitive from the data flow that the code needs.
@@ -146,6 +150,66 @@ Unrelated property updates do not require that reader to run again.
Signals also work for objects when the whole value has one identity and changes as a unit.
See [Stores](/concepts/stores) for store setters, projections, reconciliation, and optimistic stores.
+### Replace a reducer with store functions
+
+A `useReducer` reducer returns a new state object for every action.
+With a store, each action becomes a function that changes the draft in place.
+
+#### React
+
+```tsx
+type Action = { type: "toggle"; id: string };
+
+function reducer(state: { todos: Todo[] }, action: Action) {
+ switch (action.type) {
+ case "toggle":
+ return {
+ todos: state.todos.map((todo) =>
+ todo.id === action.id ? { ...todo, done: !todo.done } : todo
+ ),
+ };
+ }
+}
+
+const [state, dispatch] = useReducer(reducer, { todos: [] });
+```
+
+#### Solid
+
+```tsx
+import { createStore } from "solid-js";
+
+const [state, setState] = createStore({ todos: [] as Todo[] });
+
+function toggle(id: string) {
+ setState((draft) => {
+ const todo = draft.todos.find((item) => item.id === id);
+ if (todo) todo.done = !todo.done;
+ });
+}
+```
+
+Toggle one todo and only the JSX that reads that todo's `done` updates; the other rows do not run.
+Named functions such as `toggle` take the place of `dispatch` and action objects, and adding an item is `draft.todos.push(todo)` inside the setter.
+
+:::pitfall[Copying the state inside a store setter]
+The reducer habit of building a new array carries over without an error:
+
+```tsx
+// Avoid: a fresh array replaces the one every reader depends on
+setState((draft) => {
+ draft.todos = draft.todos.map((todo) =>
+ todo.id === id ? { ...todo, done: true } : todo
+ );
+});
+```
+
+Every reader of `state.todos` runs again, and `For` recreates the toggled row instead of updating it.
+With attribution enabled, development reports [`[IMMUTABLE_UPDATE_IN_STORE]`](/reference/solid-js/diagnostics/immutable-update-in-store).
+Change the item in place, as `toggle` does above.
+A write outside the setter, such as `state.todos.push(todo)`, is ignored, and development reports [`[STORE_WRITE_OUTSIDE_SETTER]`](/reference/solid-js/diagnostics/store-write-outside-setter).
+:::
+
## Derive values from state
React can calculate a value during render and can use `useMemo` as a performance optimization.
@@ -174,7 +238,7 @@ return (
{fullName()}
>
);
@@ -286,6 +350,60 @@ Not every React effect becomes a Solid effect:
This distinction is often more useful than translating a dependency array mechanically.
+:::pitfall[An equal result still re-runs the effect]
+React compares each dependency with the previous render and skips the effect when none changed.
+Solid runs the effect function, and the cleanup before it, whenever a value read by the compute function changes, even when the compute function returns an equal result:
+
+```tsx
+// Avoid: restarts the timer on every change to count
+createEffect(
+ () => count() > 100,
+ (large) => {
+ const id = setTimeout(() => report(large), 5000);
+ return () => clearTimeout(id);
+ }
+);
+
+// Prefer: the memo stops the update when its result is unchanged
+const large = createMemo(() => count() > 100);
+createEffect(large, (isLarge) => {
+ const id = setTimeout(() => report(isLarge), 5000);
+ return () => clearTimeout(id);
+});
+```
+
+Change `count` from 1 to 2 and the `Avoid` version clears its timer and starts a new one, so a count that keeps changing never reports.
+The `Prefer` version leaves the timer running, because the memo's value is still `false`.
+:::
+
+### Run setup once on mount
+
+A `useEffect` with an empty dependency array, such as one that starts an interval and returns `() => clearInterval(id)`, runs once after mount and cleans up on unmount.
+Solid's [`onSettled`](/reference/solid-js/lifecycle-actions/on-settled) has the same shape:
+
+```tsx
+import { createSignal, onSettled } from "solid-js";
+
+function Clock() {
+ const [now, setNow] = createSignal(Date.now());
+
+ onSettled(() => {
+ const id = setInterval(() => setNow(Date.now()), 1000);
+ return () => clearInterval(id);
+ });
+
+ return ;
+}
+```
+
+The time advances every second, and the interval stops when the component is removed.
+`onSettled` runs its callback once, after the component has mounted and any pending async work beneath it has settled.
+Its reads are not tracked, and the function it returns runs when the component's owner is disposed.
+It does not run during server rendering.
+
+There is no separate `useLayoutEffect` timing.
+Both `onSettled` and an effect function run after Solid has written the DOM for that update, so either one can measure elements.
+
## Keep props reactive
React props are values from the current component render, so destructuring them is common.
@@ -326,6 +444,38 @@ function Greeting(props: { first: string; last: string }) {
}
```
+### Default and rest props
+
+React components often take defaults and the remaining props from the parameter pattern, as in `function Button({ variant = "primary", children, ...rest })`.
+Solid uses [`merge`](/reference/solid-js/stores/merge) and [`omit`](/reference/solid-js/stores/omit), which return objects that read through to `props` on every access:
+
+```tsx
+import { merge, omit } from "solid-js";
+import type { JSX } from "@solidjs/web";
+
+type ButtonProps = JSX.ButtonHTMLAttributes & {
+ variant?: "primary" | "ghost";
+};
+
+function Button(props: ButtonProps) {
+ const merged = merge({ variant: "primary" } as const, props);
+ const rest = omit(merged, "variant", "children");
+
+ return (
+
+ );
+}
+```
+
+`` renders a submit button with `data-variant="ghost"`.
+Change `variant` in the parent and the attribute updates on the same button element.
+`merged.children` is `props.children`; resolve it with [`children`](/reference/solid-js/components-context/children) only when the component inspects or reuses the rendered children.
+
+An explicit `undefined` is a value in `merge`.
+`