diff --git a/website/docs/produce.mdx b/website/docs/produce.mdx index b1747689..cdd23935 100644 --- a/website/docs/produce.mdx +++ b/website/docs/produce.mdx @@ -9,8 +9,7 @@ title: Using produce data-ea-type="image" className="horizontal bordered" > - -
+
egghead.io lesson 3: Simplifying deep updates with _produce_ @@ -81,6 +80,8 @@ expect(nextState[0]).toBe(baseState[0]) expect(nextState[1]).not.toBe(baseState[1]) ``` +For advanced TypeScript typing scenarios, see [Using TypeScript or Flow](./typescript.mdx). + ### Terminology - `(base)state`, the immutable state passed to `produce` diff --git a/website/docs/typescript.mdx b/website/docs/typescript.mdx index cdfd7316..73d227d3 100644 --- a/website/docs/typescript.mdx +++ b/website/docs/typescript.mdx @@ -10,8 +10,7 @@ sidebar_label: TypeScript / Flow data-ea-type="image" className="horizontal bordered" > - -
+
egghead.io lesson 12: Immer + TypeScript @@ -64,6 +63,67 @@ This ensures that the only place you can modify your state is in your produce ca 2. You can use the utility type `Immutable` to recursively make an entire type tree read-only, e.g.: `type ReadonlyState = Immutable`. 3. Immer won't automatically wrap all returned types in `Immutable` if the original type of the input state wasn't immutable. This is to make sure it doesn't break code bases that don't use immutable types. +## Typing reusable recipes + +If you want to pass a recipe callback around, you can type it using `Draft` and allow either `void` or a replacement state as the return value. + +```ts +import {produce} from "immer" +import type {Draft} from "immer" + +type Recipe = (draft: Draft) => void | S + +function updateState( + setState: (updater: (prev: S) => S) => void, + recipe: Recipe +) { + setState(prev => produce(prev, recipe)) +} +``` + +### React example + +A common usage in React is to wrap `setState` with `produce` so callers can provide a typed recipe. + +```tsx +import {produce} from "immer" +import type {Draft} from "immer" +import {useCallback, useState} from "react" + +type State = { + foo: string + bar: string +} + +const initialState: State = { + foo: "", + bar: "" +} + +type Recipe = (draft: Draft) => void | State + +export function Example() { + const [state, setState] = useState(initialState) + + const updateState = useCallback((recipe: Recipe) => { + setState(prev => produce(prev, recipe)) + }, []) + + return ( + + ) +} +``` + ## Tips for curried producers We try to inference as much as possible. So if a curried producer is created and directly passed to another function, we can infer the type from there. This works well with for example React: