immerjs / use-immer

Use immer to drive state with a React hooks
MIT License
4.04k stars 92 forks source link

Feature Request: Add useImmerDraft hook #92

Closed bglgwyng closed 1 year ago

bglgwyng commented 3 years ago

I rethought this issue #91, and found that this is not general enough. Here I suggest a more general solution that can be applied to arbitrary disjoint union type state and then make us happier with TypeScript + use-immer.

https://github.com/bglgwyng/use-immer/blob/use-immer-draft/src/index.ts I wrote PoC of useImmerDraft, which returns a draft object and its finish.

type Dog = {
  name: string,
  age: number,
};

function SomeComponent() {
  const [dog, updateDog] = useImmerDraft<Dog | null>(null);
  return (
    dog
    ? <button 
         onClick={() => {
           dog.age += 1;
           updateDog(); // no argument!
         }}>
        Increase age! {dog.age}
      </button>
    :  <button onClick={() => updateDog({ name: "John", age: 1 })}>New dog!</button>
  );
}

dog is a draft object. We can get its properties in primitive type and use them in rendering and other logics. The updateDog call without any argument applies the recorded patches and finishes the draft. I think this design is the most type guard friendly one which is possible in TypeScript.

You can use useImmerDraft not only with nullable types but also with arbitrary disjoint union types.

I have a few minor thoughts on it.

mweststrate commented 1 year ago

Per Immer philosophy, drafts are expected to be shortlived, to which no references should be hold, and imho the updateDog() call is an anti pattern that is very error prone.