immer
immer copied to clipboard
How to type a custom produce function?
🙋♂ Question
Hey folks. I'm using Immer with React and I want to create a custom produce function that does something with the new state. I want to pass this produce function down to components so they can call it to update the state, and so that I can do something with the newly updated state. I can't seem to figure out how to type that function however. It seems that ValidRecipeReturnType
isn't exported. I was able to do it with use-immer
's DraftFunction
but use-immer
doesn't return the new state after it's set.
Link to repro
https://codesandbox.io/s/festive-browser-v2wj8?file=/src/App.tsx
How can I type the recipe
param from updateState
?
Environment
Latest React and Immer.
Thank you!
I use an approach like this in my applications:
type Recipe = (arg0: Draft<MyState>) => void | MyState;
RTK-Query also annotates the 'recipe' approach: https://github.com/reduxjs/redux-toolkit/blob/2425f02fe68f7e186c21e009605022013778a2c5/packages/toolkit/src/query/core/buildThunks.ts#L156
Here is a fork of your sandbox that has working annotation:
import produce from "immer";
import type { Draft } from "immer";
import { useCallback, useState } from "react";
import "./styles.css";
type MyState = {
foo: string,
bar: string
};
const initialState = {
foo: "",
bar: "",
}
type Recipe = (arg0: Draft<MyState>) => void | MyState;
export default function App() {
const [state, setState] = useState<MyState>(initialState);
const updateState = useCallback((recipe: Recipe) => {
setState((previousState) => produce(previousState, recipe));
}, [setState]);
const recipeCallback: Recipe = (state) => {
state.foo = "Muffin Man";
};
return (
<div className="App">
<h1>Hello {state?.foo}</h1>
<button
onClick={() => {
updateState(cb);
}}
type="button"
>
click
</button>
</div>
);
}
Unrelated, but check the React docs on useState
here: https://reactjs.org/docs/hooks-reference.html#functional-updates
If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.
This is important because the state update can now run outside of the render context/timing. I'm not a react render wizard, so I can't tell you how this affects batching or render timing, but it's good to use the callback notation when computing the next state from the previous. Good luck!