Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to type a custom produce function? #898

Open
alexturpin opened this issue Jan 20, 2022 · 1 comment
Open

How to type a custom produce function? #898

alexturpin opened this issue Jan 20, 2022 · 1 comment
Labels

Comments

@alexturpin
Copy link

alexturpin commented Jan 20, 2022

🙋‍♂ 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!

@bever1337
Copy link

bever1337 commented Feb 26, 2023

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants