GithubHelp home page GithubHelp logo

use-immer's People

Contributors

blackshot avatar dependabot[bot] avatar exkazuu avatar fredyc avatar hoilzz avatar kelvin9314 avatar lukahartwig avatar lukyth avatar mweststrate avatar nappy avatar omerman avatar pbomb avatar pelotom avatar plasmoxy avatar ronnyhaase avatar sophiebits avatar stsourlidakis avatar thisjeremiah avatar w3bdesign avatar werehamster avatar yifanwww avatar yujingz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

use-immer's Issues

[BUG]: Unecessary re-render

  const [values, setResources] = useImmer({ })

  useEffect(() => {
    (async () => {
      const { data } = await api.get("v2/api-docs")
      setResources(() => data.paths)
    })();
  }, [setResources])

this cause many re-renders because setResource always a new instance


I believe we must add useCallback on line below:

updater => {


the final code would be like this:

return [val, useCallback(newValue =>
    updateValue(produce(newValue))
  , [updateValue])

When using a class with constructor, immer does not detect changes

Hi guys, I don't know if this is a bug, but if it isn't, should be documented somewhere.

repro case:

https://stackblitz.com/edit/react-ts-qigunm

TLDR:

if you have class in the state, if this class has a constructor, and you pass values to it, when you try to modify a property of it on the reducer, immer does not detect that the property as changed, therefore does not trigger a rerender of the component and it stays the same value forever,

infos:

  • if you don't use constructor, and just create a plain object, it works.
  • if you still keep the constructor, and you do not pass values to it, it works!
  • with empty constructor, and you still pass values, it works
  • I tried using useReducer, and then passing the a reducer with produce, so I could pass a PatchListener to log updates, and yeah, it does not log any change

Help.

ps: let me know if I should address this one on the immer repository..

ESLint no-void warnings

When using a library like standard that uses ESLint under the hood, you'll probably see errors thrown regarding the use of void in Immer Reducers thanks to the no-void rule.

e.x.

PS ...> npx semistandard --fix
semistandard: Semicolons For All! (https://github.com/standard/semistandard)
  .\src\Context.js:47:7: Expected 'undefined' and instead saw 'void'.
  .\src\Context.js:48:7: Expected 'undefined' and instead saw 'void'.
  .\src\Context.js:49:7: Expected 'undefined' and instead saw 'void'.

In general, this isn't an issue as in the ESLint config you could just disable the rule or add /* eslint-disable no-void */ to the top of whatever file you're using void in. I'm not a fan of either of these solutions so instead, I've been writing my Immer Reducers like so:

export const globalImmerReducer = (state, action, update = () => {}) => {
  switch (action.type) {
    case actionTypes.SET_VALUE:
      update(state.number = action.value);
      update(state.numberString = action.value.toString());

      return;

    case actionTypes.SOME_ACTION:
      return update(state.someState= action.someValue);

    default:
      return console.warn(`Unknown action type: ${action.type}`);
  }
};

The use of the no return update() function is the key here since it really just emulates the behavior of void. This has been working fine for me for some time and have not noticed any issue whatsoever but I still wanted to make sure this was alright before I start pushing this stuff out to production.

  1. Is this a valid approach?
  2. Are there any side effects I should be on the lookout for?
  3. Should this be included in the README as either the default approach or in a "if you use ESLint..." section?

Why does `useImmer()` not return `ImmerHook`?

The return type of useImmer() and the ImmerHook types are slightly different. Is there a reason for that difference? In particular, why is the second element of the useImmer() return tuple not typed as Updater? For reference:

(f: (draft: Draft<S>) => void | S) => void // Updater type
(f: ((draft: Draft<S> | S) => void) | S) => void // Second element of the useImmer tuple

Support recursive immer

My apologies if this is already handled. I'm digging through a codebase at work and I saw a comment that we had a custom hook to add recursive immer handling to use-immer.

I just wanted to capture this here so I can link the issue in the code, and we have somewhere to track it.

Here is the comment and the code in question:

// The built-in useImmer doesn't support recursively calling the
// update function, e.g. updateFoo(draft => { updateFoo(draft => {}); });
// It only applies the last update. This wraps things so that it does,
// but ideally we'd fix this problem in use-immer itself via PR.
// TODO: Fix upstream in use-immer
function useRecursiveImmer<T>(initialValue: T) {
  const [value, updateValue] = useImmer<T>(initialValue);
  let isUpdating = false;
  let draft: Draft<T> | null = null;

  const updateValueRecursive: typeof updateValue = (callback) => {
    if (isUpdating && draft) {
      callback(draft);
    } else {
      isUpdating = true;
      updateValue((_draft) => {
        draft = _draft;
        callback(draft);
        isUpdating = false;
        draft = null;
      });
    }
  };

  // Needs to be seen as a tuple, not an array
  return [value, updateValueRecursive] as [typeof value, typeof updateValue];
}

Again, apologies if this is something that has already been addressed, it's not something I'm focused on at the moment. Just trying to be a good citizen.

Hope all is well!

useImmer with maps

This code below does not seem to be working.

const [state, setState] = useImmer<Map<string, object>>(new Map());

const addToMap = (key: string, val: object) => {
    setState((draft) => {
        draft.set(key, val)
    })
}

It does not seem to be creating a new reference. Its also not stated anywhere, is this Immer version compatable with maps?

[useImmerReducer] reducer gets called twice on the initial dispatch - a bug?

import React from "react"
import { useImmerReducer } from "use-immer"

const initialState = { count: 0 }

function reducer(draft, action) {
  console.log(action.type) // --- here

  switch (action.type) {
    case "reset":
      return initialState
    case "increment":
      return void draft.count++
    case "decrement":
      return void draft.count--
  }
}

function Counter() {
  const [state, dispatch] = useImmerReducer(reducer, initialState)
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
    </>
  )
}

This is the example code, and I just added console.log line before the switch.
If you click on any of the buttons on this example page, you will see the initial dispatch is run twice.

React_App

But it doesn't run twice again after the initial dispatch, and it logs just once. Is the initial twice call how it's supposed to be?

Post-write callback?

I'm looking to do something like this, but state isn't updated at this point.

dispatch((draft) => {
  const idx = draft.someArray.indexOf(val);
  idx < 0 ? draft.someArray.push(val) : draft.someArray.splice(idx, 1);
  return draft;
});
state.callback(state.someArray); // someArray is out of date

Is there a way to get around this? Or maybe this goes against the philosophy here?

immer version

I use the immer version 3.1.0.
I got the npm warn.
Can you set the peerDependencies with "immer": ">= 2.0.0"

Suggestion: useStateMachine

Hi, I wanted to share a simple hook I've built on top of use-immer that I've found very useful, and ask if there's any interest in including it in the library. I call it useStateMachine, and (adapting the example from your README), one uses it like this:

interface State {
  count: number;
}

const initialState: State = { count: 0 };

const transitions = (state: State) => ({
  reset() {
    return initialState;
  },
  increment() {
    state.count++;
  },
  decrement() {
    state.count--;
  },
});

function Counter() {

  const {
   count,
   reset,
   increment,
   decrement
  } = useStateMachine(initialState, transitions);

  return (
    <>
      Count: {count}
      <button onClick={reset}>Reset</button>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </>
  );
}

The API bears an obvious resemblance to that of useImmerReducer/useReducer, but with some important differences:

  • instead of switching on an action type, one simply writes "methods" which mutate the state (or return a fresh one)
  • instead of getting back a single dispatch function, one gets back an object of callbacks corresponding to the methods

I find this pattern much nicer to use than standard reducers for a few reasons:

  1. writing methods is nicer than putting everything into a big switch statement and having to break/return from each case
  2. the returned callbacks can be passed directly as props to children (rather than wrapping with arg => dispatch({ type: 'foo', arg }) -- blech!), and they are properly memoized!
  3. in TypeScript, one doesn't need to make an Action union type and keep it up to date; everything is inferred from the method types

The hook is very simple; here's the code for it:

import { useCallback, useMemo } from 'react';
import { useImmerReducer } from 'use-immer';

export type Transitions<
  S extends object = any,
  R extends Record<string, (...args: any[]) => S | void> = any
> = (s: S) => R;

export type StateFor<T extends Transitions> = T extends Transitions<infer S> ? S : never;

export type ActionFor<T extends Transitions> = T extends Transitions<any, infer R>
  ? { [T in keyof R]: { type: T; payload: Parameters<R[T]> } }[keyof R]
  : never;

export type CallbacksFor<T extends Transitions> = {
  [K in ActionFor<T>['type']]: (...payload: ActionByType<ActionFor<T>, K>['payload']) => void
};

export type ActionByType<A, K> = A extends { type: infer K2 } ? (K extends K2 ? A : never) : never;

export default function useStateMachine<T extends Transitions>(
  initialState: StateFor<T>,
  transitions: T,
): StateFor<T> & CallbacksFor<T> {
  const reducer = useCallback(
    (state: StateFor<T>, action: ActionFor<T>) =>
      transitions(state)[action.type](...action.payload),
    [transitions],
  );
  const [state, dispatch] = useImmerReducer(reducer, initialState);
  const actionTypes: ActionFor<T>['type'][] = Object.keys(transitions(initialState));
  const callbacks = useMemo(
    () =>
      actionTypes.reduce(
        (accum, type) => {
          accum[type] = (...payload) => dispatch({ type, payload } as ActionFor<T>);
          return accum;
        },
        {} as CallbacksFor<T>,
      ),
    actionTypes,
  );
  return { ...state, ...callbacks };
}

If you think this would make a good addition to the library I'd be happy to open a PR.

support patches feature from `immer`?

when using use-immer, I found that it no way to use the patches feature

import { useImmer } from 'use-immer';
import { enablePatches } from 'immer';
enablePatches();

const App = props => {
  const [a, setA] = useImmer({ a: 1 });

  return (
    <div>
      <pre>{JSON.stringify(a)}</pre>
      <button
        onClick={() => {
          setA(
            draft => {
              draft.a = 10;
            },
            () => {
              console.log('this is never called');
            }
          );
        }}
      >
        setA
      </button>
    </div>
  );
};

export default App;

but I don't really want to refactor all my useImmer to useState(produce)

Can't get the types to work properly with `useImmerReducer`

Hi there,

❤️ immer, and ❤️this little gem on top, making it even easier/more readable to use immer for my reducers.

However, when using useImmerReducer I cannot get the resulting state and dispatch to be typed correctly, I get any for both instead.

Here's a CodeSandbox showing the issue: https://codesandbox.io/s/p75vmyvo1j

You'll find a working example using immer's + React's useReducer and Reducer type as well as a version using useImmerReducer. They both technically work perfectly fine, it's just a typing issue. See the comments in the code for more explanation.

I know it's not a huge deal, but if I can help and improve this little 💎then that'd be awesome!

✌️

return void draft.count++ in TS

When using useImmerReducer, I have return void draft.count++ but it doesn't seem to work. I'm quite new to both immer and TypeScript. If anyone knows what's going on, I'd really appreciate your help.

Thank you.

Doesn't work in IE11

Hi :)

Today I was bitten by IE11 when using this little library, because it broke our build (since we don't transpile our node_modules). We learned that the following code line:

https://github.com/mweststrate/use-immer/blob/d7bd530627a368dc74dde5ae9e3fccb9c4c4ea6c/index.js#L5

was responsible for it. More precisely the lack of support for destructuring in IE11 + the omitted build step resulted in a syntax error which crashed our app.

I do realise why you decided to omit a build step for this tiny function, but following your comment and reasoning in mobxjs/mobx-react-lite#63 (comment): I was caught off-guard and definitely did not expect this library to break our app in IE11, especially since immer is also working without a problem.

I hope you see a reason to transpile even this tiny function to valid ES5-compatible code and maybe you'll find some time to add the build step.

Alternatively I could prepare a PR that gets rid of the destructuring similar to this:

module.exports.useImmer = function useImmer(initialValue) {
  const state = React.useState(initialValue);

  return [
    state[0],
    updater => {
      state[1](produce(updater));
    }
  ];
}

Since we needed a fix asap, our solution was to just copypasta the code in our codebase. We use typescript, so I tried to combine your code with the provided typings from index.d.ts:

import React from 'react';
import produce, { Draft } from 'immer';

type Recipe<S = any> = (this: Draft<S>, draftState: Draft<S>) => void | S;
type Update<S = any> = (recipe: Recipe<S>) => void;

export const useImmer = <S = any>(initialValue: S): [S, Update<S>] => {
  const [value, updateValue] = React.useState(initialValue);

  return [
    value,
    (updater) => {
      updateValue((state) => produce(state, updater));
	  // ^ type error much
    },
  ];
};

See https://codesandbox.io/s/5ymlw2wjrx - but that didn't work well, since typescript was complaining about a type mismatch.

Our solution was to change the Recipe typing like this:

// does not work
type Recipe<S = any> = (this: Draft<S>, draftState: Draft<S>) => void | S;

// works
type Recipe<S = any> = (this: Draft<S>, draftState: Draft<S>) => void;

So we just omitted | S from the returned type and that seemed to fix it, but I am unsure why it was there in the first place. Maybe you can elaborate on that?

Anyways, thanks for your time & work.
Looking forward to an answer :)

upgrade immer?

Is there a reason [email protected] is used in this module?

I noticed in our bundling that multiple versions of immer is bundled:

WARNING in immer
  Multiple versions of immer found:
    1.12.1 /project/~/use-immer/~/immer
    2.1.1 /project/~/@jimengio/rex/~/immer

when useImmer use in an async function

Now I change the input value in a async request,
then useImmer's produce 'setObj' can't render the React function component.
It do nothing?
the code:
image

the print:
image

React Version:^16.12.0
use-immer Version:^0.5.1

Is this package stable?

I'm not sure if it's intentional or not but the fact that the package version is 0.y.z means that any change is considered a major semver change (https://semver.org/#spec-item-4) which interferes with automatic dependency management tools (like npm update) which tend to strictly follow semver. If you consider this package stable, please consider incrementing to version 1!

Add helper to wrap state from other hooks?

Running into a situation that I think would be nicely handled in use-immer since it seems somewhat common...

If you a value is scoped to your component, then the current useImmer works well. But if you don't have control over the value (eg. it's passed from a parent, or it's gotten from local storage, etc.) then you have the deal with the annoyance of using a callback to set the new state.

But it would be nice to be able to do something like:

function Example() {
  // The "user" lives in Local Storage and should be updated on changes.
  // This returns `[user, setUser]`, but you want to use immer to update it.
  const userState = useLocalStorage('user')

  // So you can wrap the state tuple easily and have this new hook handle 
  // the callback logic for you to save.
  const [user, updateUser] = useImmerState(userState)
  
  // Here the `updateValue` knows to call `setValue` under the covers 
  // to save the update in Local Storage.
  updateUser(user => {
    user.name = 'New Name'
  })
}

Since the [value, setValue] pattern is widespread, this new hook would be very flexible and could be used to augment any existing tuple-returning state hook.

What do you think?

Updating an array

Hello, i'm currently pretty new to immer as well as react hooks and currently refactoring a bit of code.
I got a little trouble with updating an array.

Imagine a wrapper component that is only there to fetch some data and show another component after the fetching is complete, while showing some loading view during fetching.

function MyWrapper() {
	const [data, updateData] = useImmer([]);

	useEffect(() => {
		preFetch();
	}, []);

	function preFetch() {
		try {
			Promise.all([
				axios.get('https://jsonplaceholder.typicode.com/users'),
				new Promise(resolve => {
					setTimeout(() => {
						resolve();
					}, 1000);
				})
			]).then(([response]) => {
				if (response && response.data) {
					updateData(draft => {
						draft.data = response.data
					});
				}
			});
		} catch (error) {
			console.log('Error catched', error);
		}
	}

	return data ? <MyList data={data} /> : <MyLoader/>;
    }

I expected this to work, but i get an error message:

immer.module.js:930 Uncaught Error: Immer only supports setting array indices and the 'length' property
at Object.arrayTraps.set (immer.module.js:930)

After a lot of trial and error i found these two approaches to work:

updateData(() => { 
    return response.data;
})
updateData(draft => (draft = draft.concat(response.data)));

which worked for me, but i don't see why the first approach doesn't. I also tried it using produce directly instead of useImmer like so:

const [data, updateData] = useState([]);

// ...

function preFetch() {
// ...
updateData(
    produce(draft => {
		draft.data = response.data;
    })
);
}

with the same outcome.

Any help would be greatly appreciated.

P.S: Would it be considered bad practice to replace an entire array (even if it's an empty one like in this example?) and therefore should go with the concat approach?

[Feature request] Dispatch thunks in useImmerReducer ?

Hey everyone, thanks for making this package.

It would be nice to be able to also dispatch thunks from the useImmerReducer hook.

Instead of dispatching an action, we would dispatch an async function, just like in redux-thunk.

It would really help with async actions involving API calls.

Is it possible?

Expose the patch

Is it possible to expose the patch object in a reducer somehow or for this we should use vanilla Immer?

Thanks and awesome work!

Missing support for React 17.0.1

Hello,
I was trying to install immer into a NextJS project, I get this error:

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR! react@"17.0.1" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.8.0" from [email protected]
npm ERR! node_modules/use-immer
npm ERR! use-immer@"0.4.1" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
npm ERR! See /Users/sviluppo/.npm/eresolve-report.txt for a full report.

npm ERR! A complete log of this run can be found in:
npm ERR! /Users/sviluppo/.npm/_logs/2020-11-03T11_02_44_185Z-debug.log

LOG FILE: https://pastebin.com/4wRQDs9b

Updater function doesn't return next state (unlike Immer's produce)

Hi, 🙂 I would like to do a sort of "optimistic" update mechanics but I found out that the updater function only returns me undefined instead of the next state when I wanna do update to the backend. Here is my hypothetical code snippet:

function PersonUpdater() {
  const personDataFromTheBackend = useSelector((state) => state.person);
  const dispatch = useDispatch();

  const [person, updatePerson] = useImmer({
    name: 'Glenn',
    age: 30,
  });

  useEffect(() => {
    updatePerson(() => personDataFromTheBackend);
  }, [personDataFromTheBackend]);

  return (
    <div className="PersonUpdater">
      <h1>
        Hello {person.name} ({person.age})
      </h1>
      <br />
      <button onClick={becomeOlder}>Older</button>
    </div>
  );

  function becomeOlder() {
    const updatedPerson = updatePerson((draft) => {
      draft.age++;
    });

    // updatedPerson is undefined
    console.log(updatedPerson);

    // this will make a call to the backend
    dispatch(savePerson(updatedPerson));
  }
}

I know I could make use of the next state of (updated) person in useEffect hook, but this would make unnecessary network call when the component first loaded/mounted.

useEffect(() => {
  // this will make an unnecessary update call to the backend when the component first rendered
  dispatch(savePerson(person));
}, [person]);

Not sure if this is by-design though. 🙄

@mweststrate Thank you so much for this awesome Immer's library btw 🙇🏻‍♂️

Update by reference (like MST/Mobx)

I want to update an element in a table by passing a reference to it via props.

In MST this works without a problem, you pass the reference, there are actions on the reference, which update the data in the right place.

How to do something similar (update via reference) with use-immer?

My current solution using indexes and referencing the root directly is the oldschool dirty solution which is exactly what I wish to avoid as it will not scale when those objects get arrays/maps of nested objects with their own nested arrays etc... with each level having its own component.

<div>
{store.conditions.all.map((c, i) => {
          const setName = name =>
            setStore(s => {
              // how to replace this with
              // c.name = name
              // using only c ref inside the <Condition /> ?
              s.conditions.all[i].name = name
            })

          const remove = () =>
            setStore(s => {
              s.conditions.all.splice(i, 1)
            })

          return (
            <Condition
              index={i}
              key={i}
              condition={c}
              remove={remove}
              setName={setName}
            />
          )
        })}
</div>

https://codesandbox.io/s/use-immer-example-ekhj9?file=/src/App.js

Why does the hook return a mutable value?

export function useImmer(initialValue: any) {
  const [val, updateValue] = useState(initialValue);
  return [
    val,

I was really surprised that this didn't return freeze(val, true) or similar. This seems to defeat the purpose of insuring immutability to me. What's the thinking behind this? Thanks!

Can you folks push up the code/tags for the 0.5.2 release?

Thanks for maintaining use-immer! It and the whole ecosystem are great.

I'm trying to understand what's in the 0.5.2 release on NPM [1], but it looks like the last code that was pushed up here was for 0.5.1. I could download the package from NPM and diff it, but it would be great just to be able to look at it in Github.

A second ask, if easy, is can you push up git tags for the 0.5.x release series? No problem though if that's not easy.

[1] https://www.npmjs.com/package/use-immer

Undo Feature

So I have a use case in which it would be nice to be able to undo an immer action for optimistic updates. Essentially I'd like to change the local state with the immer hook and then post to my backend to update the database. Should this update fail or be invalid, I'd like to revoke the change to the local state. Is there an undo feature for the hook?

alternate syntax for reducer

what about something like this instead?

function reducer(draft, action, setState) {
  switch (action.type) {
    case "reset":
      setState( initialstate) ;
      break
    case "increment":
      draft.count++;
      break
    case "decrement":
      draft.count--;
      break
  }
}```

Support immer ^4.x.x

I see errors while updating my project since I've updated immer to 4.0.0
although everything works without issues

Thank you for your work!

call after immer state update not using next state

A bit stumped here if this is a bug or I'm doing something wrong. I'm trying to push a new item into an array, then update the db based on what is within the state. The view renders the state correctly with next state but the function call is still using the old state.

Unsure if these producers are async in some form, but according to the Typescript typings they aren't, so I cannot await the state update before the db call.

Pretty much trying to update the state, then execute the register mutation. The user state still has an empty array when the mutation is called, but the view renders it correctly, so the state does eventually get updated.

<SecurityStep
  onComplete={(data) => {
    setUser((draft) => {
      draft.credentials.create.push({
        type: CredentialType.Basic,
        token: data.password,
      });
    });

    register({ variables: { data: user } }).then(() =>
      setStep(ProgressionStep.COMPlETE)
    );
  }}
/>

immer state initialization

  const [user, setUser] = useImmer<UserCreateInput>({
    first_name: null,
    last_name: null,
    email: null,
    avatar: null,
    credentials: {
      create: [],
    },
  })

Typescript error `Type 'T' is not assignable to type 'Draft<T> | undefined'`

I get multiple errors in the form
Type T is not assignable to type Draft<T>.

Here is my code:

export const useManageFinanceInfo = <K extends keyof RestPayoutInfo>(
	close: () => unknown,
	payoutId: PayoutMethodCurrentId,
	fields: Pick<RestPayoutInfo, K>,
) => {
	type Action = InputFieldActions<K> | PayoutInfoFormAction

	const getHomogeneousSubmitErrorValues = useCallback((val: boolean): Record<
		keyof typeof fields,
		boolean
	> => {
		// @ts-ignore
		return mapObj(fields, () => val)
	}, [])

	const { initVals, activePayoutMethod } = useDashboardSelector((st) => ({
		initVals: pick(st.finance.appData!.payoutInfo, Object.keys(fields)),
		activePayoutMethod: st.finance.appData?.payoutInfo.currentPayoutMethod === "WIRE2",
	}))

	const initState = useMemo(
		() => ({
			vals: initVals,
			focusOn: undefined as undefined | K,
			submitErrorsFields: getHomogeneousSubmitErrorValues(false),
		}),
		// eslint-disable-next-line react-hooks/exhaustive-deps
		[],
	)

	const [state, setState] = useImmer(initState)

	const dispatch = (action: Action) => {
		switch (action.type) {
			case "focus-start":
				setState((st) => {
					st.focusOn = action.payload // Error 1
					st.submitErrorsFields[action.payload] = false // Error 2 
				})
				break

.....

Errors:

  1. Type 'K' is not assignable to type 'Draft | undefined'.
    Type '"bankAccountId" | "nameOnAccount" | "address" | "country" | "state" | "bankName" | "bankId" | "bankAddress" | "bankCountry" | "bankState" | "minimalPayoutAmount" | "paypalEmail" | "currentPayoutMethod"' is not assignable to type 'Draft | undefined'.
    Type '"bankAccountId"' is not assignable to type 'Draft | undefined'.
    Type 'K' is not assignable to type 'Draft'.
    Type '"bankAccountId" | "nameOnAccount" | "address" | "country" | "state" | "bankName" | "bankId" | "bankAddress" | "bankCountry" | "bankState" | "minimalPayoutAmount" | "paypalEmail" | "currentPayoutMethod"' is not assignable to type 'Draft'.
    Type '"bankAccountId"' is not assignable to type 'Draft'.ts(2322)

  2. Type 'boolean' is not assignable to type 'Draft<Record<K, boolean>>[K]'.ts(2322)

Somewhat similar to 2, I have also seen string is not assignable to Draft<string>. I understand that in this case we have a relatively complex generic. But I have seen this kind of error in simpler generic form as well.

Versions:
TS - 4.0.4
useImmer - 0.4.2

Use async/await with useImmerReducer

Is there any way handle async operations inside reducer?

Currently, if I try something like this:

const reducer = async (draft, action) => {
		switch (action.type) {
			case 'HANDLE_INFO_INPUT_CHANGE': {
				const { name, value } = action.target;
				const allPatients = await userApi.getPatients(true);
				draft.info[name] = value;
				break;
			}
.......

I'm getting Unhandled Rejection (TypeError): Cannot perform 'get' on a proxy that has been revoked.

Thoughts?

Type Definitons: React.useState can be initialized by factory function while useImmer can not

Since useImmer() is a tiny wrapper around native useState() it's initial value can be defined by factory function:

const [state] = useImmer(() => ({
  foo: 123
}));

But inferred type of state is () => { foo: number; } instead of { foo: number; }.
Because signature of React.useState() has overloading for this case:

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];

While useImmer() signature accepts only raw state object:

function useImmer<S = any>(initialValue: S ): [S, (f: (draft: Draft<S>) => void | S) => void];

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.