immerjs / use-immer Goto Github PK
View Code? Open in Web Editor NEWUse immer to drive state with a React hooks
License: MIT License
Use immer to drive state with a React hooks
License: MIT License
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:
Line 16 in 7e3bc90
the final code would be like this:
return [val, useCallback(newValue =>
updateValue(produce(newValue))
, [updateValue])
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:
Help.
ps: let me know if I should address this one on the immer repository..
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.
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
Reproduced here:
https://codesandbox.io/s/use-immer-typing-vp697?file=/src/App.tsx
The example compiles without errors using use-immer-0.4.2.
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!
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?
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.
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?
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?
Hello!
Im getting the error Cannot perform 'get' on a proxy that has been revoked
in jest tests.
You can find the error reproduced here https://codesandbox.io/s/old-platform-yeq09
In browser everything works like a charm. Only test fails. Maybe Im missing something.
Could you help?
I use the immer version 3.1.0
.
I got the npm warn.
Can you set the peerDependencies with "immer": ">= 2.0.0"
This code can execute but it has an error on the reducer.
https://codesandbox.io/s/custom-hook-v7-1-e6yl0b?file=/src/useStack.ts
Removing the generics will remove the error.
https://codesandbox.io/s/custom-hook-v72-forked-51k3h?file=/src/useStack.ts
How can I fix it?
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:
dispatch
function, one gets back an object of callbacks corresponding to the methodsI find this pattern much nicer to use than standard reducers for a few reasons:
break
/return
from each casearg => dispatch({ type: 'foo', arg })
-- blech!), and they are properly memoized!Action
union type and keep it up to date; everything is inferred from the method typesThe 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.
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)
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!
✌️
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.
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 :)
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
I'd love it if I could get rid of the if (e.target.type === 'number')
check: https://codesandbox.io/s/useimmer-demo-5ulb7
Sidenote: It would also be super amazing/ideal if I could get rid of the e.persist()
😍
Is there a way to use patches with this hook?
Thanks in advance!
code =>>
const [selectedLanguage, setselectedLanguage] = useImmer({});
setselectedLanguage({
displayName: "English",
langCode: "eng",
});
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!
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?
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?
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?
Is it possible to expose the patch object in a reducer somehow or for this we should use vanilla Immer?
Thanks and awesome work!
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
In this example, i can use the incrementDirectly button of Counter without any error :
https://codesandbox.io/s/nifty-jennings-1vmly
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 🙇🏻♂️
The third parameter initialAction
seems not implemented in useImmerReduer
.
useReducer
accepts an optional third argument,initialAction
. If provided, the initial action is applied during the initial render.
the example could be simplified:
https://codesandbox.io/s/0yyomw1l4p
but maybe it's just my personal preference. Feel free to just ignore and close this.
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
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!
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.
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?
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
}
}```
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!
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: [],
},
})
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:
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)
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
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?
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];
Per https://immerjs.github.io/immer/docs/return#producing-undefined-using-nothing, you can return the constant nothing
to set a draft to undefined
.
This fails when using useImmer
, with Type 'Nothing' is not assignable to type 'void | T | undefined'.
I suspect the machinery for nothing
is just missing from the types in this repo.
`
const [state, setState] = useImmer({
listSet: new Set([])
});
useEffect(() => {
setState(state => {
state.listSet.add(activeRouteId);
});
}, [activeKey]);
Error: [Immer] The plugin for 'MapSet' has not been loaded into Immer. To enable the plugin, import and call enableMapSet()
when initializing your application.`
use-immer
is really helpful. But I don't know what to do to solve the capture value
problem.
Look at the reproduction, when I click the add button, the value changed. But the print
method just printed the old value. How to avoid this problem?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.