immerjs / immer Goto Github PK
View Code? Open in Web Editor NEWCreate the next immutable state by mutating the current one
Home Page: https://immerjs.github.io/immer/
License: MIT License
Create the next immutable state by mutating the current one
Home Page: https://immerjs.github.io/immer/
License: MIT License
"reducure", "negclectable.", "implentation".
the pattern is now typically:
function insertItem(array, action) {
return immer(array, draft => {
/* user code */ draft.splice(action.index, 0, action.item)
})
}
So we could introduce a utility
createImmerReducer(fn: (draftState, action) => {})
which allows writing
const insertItem = createImmerReducer((draft, action) => {
/* user code */ draft.splice(action.index, 0, action.item)
})
The implementation would roughly be:
function createImmerReducer(fn) {
return (state, action) => immer(state, draft => fn(draft, action))
}
I made similar library (also auto-immutability via Proxy) but my library takes quite a different approach to implementation.
https://github.com/hex13/enter-ghost/tree/master/packages/transmutable
I don’t create copies in set
traps. Instead of that I only “record” mutations like they were events (I’ve got an array with list of “what happened”, e.g.
state.foo.bar = 3
state.baz = 4
get recorded as
[
{type: 'set', path: ['foo', 'bar'], value: 3},
{type: 'set', path: ['baz'], value: 4}
]
Then (after recording all mutations) I make a deep clone of the whole state tree (but I do dirty checking, based on recorded mutations - if something is not changed in mutation, it's copied by reference).
And then I replay recorded mutations and apply them to the clone of state.
On the other hand I've noticed that you took a different approach to implementation. From what I've seen you're making ad-hoc copies of state in Proxy and change them immediately (without recording mutations).
Which approach is better?
I think both have pros and cons.
Event-sourcing approach allows for recording all mutations. Probably in Redux setting it won't be
needed (because Redux is already kind of event-sourced) but I was experimenting with making my own state management library and event-sourcing allowed me to make such a nice dev tools for it which showed me which exact mutations occured: https://hex13.github.io/demos/todo/
Event-sourcing approach also can help with the versioning objects (undo, redo, clone, merge) but with Redux it could probably be done in some different way.
on the other hand because my implementation has two phases (1. record mutations 2. make a copy and replay mutations) and this has some shortcomings. Because it applies all changes all at once, this means state in transformer always point to old, stale version of state:
transmutable
transform({arr: [10,20]}, draft => {
draft.arr.push(30);
console.log(draft.arr); // [10, 20]
});
immer
immer({arr: [10,20]}, draft => {
draft.arr.push(30);
console.log(draft.arr); // [ 10, 20, <1 empty item> ]
});
This means that your approach (modify ad-hoc) feels more like VanillaJS. I make a push and something was pushed right away. Where in transmutable
draft always returns values from old version.
I wonder if it was possible to merge these two approaches. I need event-sourcing* but your immediate approach has also clear advantages.
Besides yours and mine there are also similar libraries made by different people.
https://github.com/engineforce/ImmutableAssign
https://github.com/Palindrom/JSONPatcherProxy (I think this one only records mutations, without transforming).
*I'm intending to make full state management event-sourced library and transmutable
is only a mean to an end.
Did you thought about integrating immer into Mobx? (just curious). Observable that would modify themselves in immutable way. That could make Mobx immutable in simple way.
@mweststrate - Can I use produce on my reducer with redux-action's handleActions(Object) method?
eg -
const reducer = (state = initState, action) => produce(state, draft =>
handleActions({
[ACTION_TYPES.FETCH_DATA]: (draft, action) => {
draft.data = action.payload
}
})
)
I noticed you have the typescript devDependency but the source code is written in JS.
Any reason why you didn't write it in TS?
See #30 for an example
I want to use minified file, but now immer doesn't have a minified version.
Getting this issue in a react-native project on android only. on iOS the app runs fine
[email protected]
I am trying out immer for a simple todos app. I have a simple reducer function like this -
Reducer( state = initialState, action) {
let method = actionList[action.type];
if(method){
const nextState = produce(state, draft_state => {
let new_state = method( draft_state , action);
return new_state
})
return nextState;
}
else return state
}
actionList
is an object which functions as a clutter free version of typical reducer (using the above function in the hindsight):
const actionList = {
ADD_TODO : (state, action) => state.push(action.todo),
TOGGLE_TODO : (state, {todo}) => {
return state.map( _todo =>{
if( _todo.id==todo.id){
_todo.completed = !_todo.completed
}
});
}
}
state
object goes like this:
const state = [
{
id :1,
text: 'sample todo',
completed : true
}
]
The issue is I am unable to get TOGGLE_TODO
to work using this approach (or any approach). To put it simply, how may I toggle the completed flag of a todo using below method:
TOGGLE_TODO : (state, {todo}) => {
// here state is the proxy of currentState generated by immer, todo is the todo object to be toggled
}
here's sandbox link if that helps better:
https://codesandbox.io/s/ymw0127r39
Hi,
I tried the performance benchmark on different versions of node and i experienced a weird drop of performances.
Here are my computer specifications:
CPU: 2,7 GHz Intel Core i5
RAM: 8 Go 1867 MHz DDR3
On 8.4.0:
performance
✓ just mutate (2ms)
✓ deepclone, then mutate (475ms)
✓ handcrafted reducer (23ms)
✓ immutableJS (66ms)
✓ immer - with autofreeze (371ms)
✓ immer - without autofreeze (181ms)
On 9.3.0:
performance
✓ just mutate (5ms)
✓ deepclone, then mutate (324ms)
✓ handcrafted reducer (19ms)
✓ immutableJS (69ms)
✓ immer - with autofreeze (510ms)
✓ immer - without autofreeze (1378ms)
I didn't found out the cause of this.
Node: 8.4.0
immer: 0.5.0
const produce = require('immer').default;
const result = produce(
{arr: [{count:1}, {count: 2}, {count: 3}]},
draft => {
draft.arr = draft.arr.filter(item => item.count > 2);
}
);
console.log(result); // throws
result.arr[0].count
seems to be troublesome, even referencing to this variable throws an error.
Result of function produce
should be a plain JS object, right? Because it seems that the result sometimes contains proxies inside.
Consider adding some warnings for this?
const produce = require('immer').default;
const result1 = produce([], draft => {
draft = {}
})
console.log(result1) // []
const result2 = produce({}, draft => {
draft = []
})
console.log(result2) // {}
Hi all!
I'm looking for feedback. If you tried / are trying this package, how do you experience it? Does it deliver what it promises? Does it help reduce amount / complexity of code? Is there anything you are missing or that could be improved in this package or the docs?
Any kind of feedback is appreciated, thanks!
immer 0.4.1, Node v8.4.0
const produce = require('immer').default;
produce([],(arr) => { console.log(arr); });
TypeError: 'getOwnPropertyDescriptor' on proxy: trap returned descriptor for property 'length' that is incompatible with the existing property in the proxy target
at Function.keys (<anonymous>)
at formatValue (util.js:392:21)
at inspect (util.js:236:10)
at format (util.js:97:24)
at Console.log (console.js:113:24)
at immer (repl:1:28)
at produce (blah/node_modules/immer/immer.js:224:29)
at repl:1:1
at ContextifyScript.Script.runInThisContext (vm.js:44:33)
at REPLServer.defaultEval (repl.js:239:29)
The current version (0.1.0) on npm does not come with the index.d.ts
file.
One of the major pain points when dealing with nested data in Javascript is not having a "null soak" (AKA existential) operator.
Coffeescript has a built-in one:
obj = { a: { b: 5 }}
# logs `5`
console.log obj.a?.b
# logs `undefined`
console.log obj.foo?.bar?.b
Immutable.js offers a similar pattern with getIn, albeit with a really-not-great API:
import { getIn } from 'immutable';
getIn({ x: { y: { z: 123 }}}, ['x', 'y', 'z']) // 123
getIn({ x: { y: { z: 123 }}}, ['x', 'q', 'p']) // undefined
I wonder if it would be possible/feasible to add such a solution for the draftState
object in Immer? I imagine it'd be really nice to be able to do stuff like:
import produce from 'immer';
const initialState = { a: { b: 5 } }
const nextState = produce(initialState, (draftState) => {
const {baz} = draftState.foo.bar;
if (typeof baz === 'undefined') {
someMutativeMethodToCreateBaz(draftState);
}
})
With proxies, this is possible. A package called safe-proxy
implements this. However I don't think an ES5 alternative would be doable (maybe with a try/catch or some really gross function.toString business?).
If this project is committed to maintaining an identical API with the ES5 version, it's probably more trouble than it's worth, unless I'm missing something?
Happy to contribute some work towards this idea because I think it could be a really neat addition, but yeah I also wouldn't be surprised if it's simply not worth doing :) mostly just wanted to start a discussion about it.
EDIT: actually, now that I think about it... having the API be totally implicit like that feels like a bad idea, since users who aren't familiar with Immer would believe that it's a native feature of the language. Could be really misleading.
We could export a helper that does it (like safe-proxy
does), but really the user can just bring their own helper if they want to be able to do that.
immer(
{user: 'user'},
draft => {
draft.user = undefined
}
)
throws Cannot read property 'Symbol(immer-proxy)' of undefined
Example --
https://stackblitz.com/edit/react-9qe1eg
If there is enough interest.
Proxy using defineProperty (the mobx way), use faux arrays and dirty check for key additions
immer
does not modify the draft value on using lodash.find
on 1.0.0
.
The test code is below:
it("processes with lodash.find", () => {
const base = [{id: 1, a: 1}, {id: 2, a: 1}]
const result = produce(base, draft => {
const obj1 = find(draft, {id: 1})
const obj2 = find(draft, {id: 2})
obj1.a = 2
obj2.a = 2
})
expect(result[0].a).toEqual(2)
expect(result[1].a).toEqual(2)
})
result:
Expected value to equal:
2
Received:
1
130 | obj2.a = 2
131 | })
> 132 | expect(result[0].a).toEqual(2)
133 | expect(result[1].a).toEqual(2)
134 | })
135 |
for reproduce: https://github.com/iruca3/immer/tree/bug_with_lodash_find
It reproduces on 1.0.0
, but does not reproduce on 0.8.5
.
I tried to fix this, but I couldn't find out why this is caused.
Currently < 3KB, but, could be smaller by
// Plain reducer
function insertItem(array, action) {
return [
...array.slice(0, action.index),
action.item,
...array.slice(action.index)
]
}
--------------------------- I think the more real world way is:
function insertItem(array, action) {
return array.slice().splice(action.index, 0, action.item)
}
// With immer
function insertItem(array, action) {
return produce(array, draft => {
draft.splice(action.index, 0, action.item)
})
}
// Plain reducer
function removeItem(array, action) {
return [
...array.slice(0, action.index),
...array.slice(action.index + 1)
];
}
--------------------------- I think the more real world way is:
function removeItem(array, action) {
return array.slice().splice(action.index, 1)
}
// With immer
function removeItem(array, action) {
return produce(array, draft => {
draft.splice(action.index, 1)
})
}
// Plain reducer
function updateObjectInArray(array, action) {
return array.map( (item, index) => {
if(index !== action.index) {
// This isn't the item we care about - keep it as-is
return item;
}
// Otherwise, this is the one we want - return an updated value
return {
...item,
...action.item
};
});
}
--------------------------- I think the more real world way is just updateObject:
function updateObject(foosMap, action) {
return {
[action.item.id]: {
...foosMap[action.item.id],
...action.item
}
}
// or more accurate (assume we just want to update bar)
return lodash.merge({}, foosMap[action.item.id], {bar: action.bar})
}
because in most cases we will convert objectArray to object by using normalizr or something else
// With immer
function updateObjectInArray(array, action) {
return produce(array, draft => {
draft[action.index] = { ...item, ...action.item}
// Alternatively, since arbitrarily deep updates are supported:
// Object.assign(draft[action.index], action.item)
})
}
some pieces of code are untouched. Lets fix!
I am not too excited about immer
as function name as it is undescriptive. What should the idiomatic name be?
Transform? Update? withMutations? Make?
(related to #24 )
idea:
immer
is called with only first argument (recipe function) it would not transform but return other function with recipe function boundrecipe
functionWHY?
it would help reuse transformation, treat them as objects. For example when using with Redux #31 or with map
support #35
const reducer = immer((state, action) => {
switch (action.type) {
case 'add':
state.arr.push(action.something);
break;
}
});
createStore(reducer, initialState);
If data is unchanged, does immer return the original reference like immutable.js? This would help when used in redux connect method to power props to react containers.
Do weird things. Try to break it.
I was investigating a solution for structural sharing of immutable JS arrays. The problem I had is that once I immutably mutate the array, it should give me a new array reference. If that old array reference still exists somewhere all the old data should still persist. However if that old array reference is dropped, then all the objects in that array that has been shadowed by the new array reference should also be garbage collected as they are no longer reachable. However I couldn't find a way to do this with just normal JS arrays or even pairing sparse arrays into a tree. What does this library do to deal with this problem?
I was thinking that JS needs some sort of weak pointers.
I have been using immer with react-native , it runs fine on ios but on android it throws error 'Cannot find variable Symbol in react native'.
possibly add an immer implementation that can be used with map/filter/forEach
ex.
const immerMap = (fn) =>
(item) => immer( item, fn )
var arr = [['1'], ['2'], ['3']];
arr.map(immerMap((item) => {item.push('new')}));
I'm not sure if this is a issue with immer, which conceptually I love, or I'm using it wrong so I'd be interested to hear thoughts on this.
I'm trying to plug immer into a relatively small React/Redux project I have on the go. In reducers it all seems to work exactly as expected as the inputs are essentially a merge of the old state and an action payload.
However I then started looking at some of my selectors and thought it would be applicable there since the output from a selector is frequently a mutated version of a portion of the state, with lookups and/or references plugged in or one-time evaluations executed and saved. Again, that more or less seemed to work until I got to the part where a reference to a different part of the state tree was being attached to my new state. Then I got the following error,
Uncaught TypeError: Cannot assign to read only property '_id' of object '#<Object>'
In my case the objects are from a Mongo DB and so _id is the first field in the object.
To express this in more familiar terms, given an example of users and todos where each todo belongs to a user, my selector might be called getUserAndTodos and would return both the original user record and include a list of their todos taken from the general todo list in the global state, as well as maybe an 'allDone' flag to indicate whether that user has completed all their todos.
Given that I am essentially modifying (by addition) the user field from state, I thought this was a good place to use 'produce'. If 'produce' returns just the user record unmodified, then it's fine. If it additionally adds the 'allDone' field calculated from the users list of todos then it's also fine, but if I filter the todo list from state and add a list of the users todos to the user object I get the error above. Note, that if I turn off autofreeze then I don't see the error.
My guess is that because I am plugging in already frozen objects into a member of the 'new state' that somehow it's getting itself confused and trying to copy them over, rather than noticing it can just use a reference? More so because I am just referencing them and they aren't a direct argument to 'produce' itself. Or perhaps more like I've entirely misunderstood how this is supposed to work.
If necessary I can try and get a sandbox up that shows the error but I thought I would ask first in case I'm just doing it all wrong.
The partial stack trace of the error is below if it's of any use.
Thanks
TypeError: Cannot assign to read only property '_id' of object '#<Object>'
finalize
node_modules/immer/dist/immer.js:226
223 | var proto = Object.getPrototypeOf(base);
224 | if (proto === null || proto === Object.prototype) {
225 | for (var key in base) {
> 226 | base[key] = finalize(base[key]);
227 | }return freeze(base);
228 | }
229 | }
View compiled
finalize
node_modules/immer/dist/immer.js:226
223 | var proto = Object.getPrototypeOf(base);
224 | if (proto === null || proto === Object.prototype) {
225 | for (var key in base) {
> 226 | base[key] = finalize(base[key]);
227 | }return freeze(base);
228 | }
229 | }
View compiled
finalizeObject
node_modules/immer/dist/immer.js:237
234 | var copy = state.copy;
235 | var base = state.base;
236 | for (var prop in copy) {
> 237 | if (copy[prop] !== base[prop]) copy[prop] = finalize(copy[prop]);
238 | }
239 | return freeze(copy);
240 | }
View compiled
finalize
node_modules/immer/dist/immer.js:212
209 | if (state.finalized === true) return state.copy;
210 | state.finalized = true;
211 | if (Array.isArray(state.base)) return finalizeArray(state);
> 212 | return finalizeObject(state);
213 | } else return state.base;
214 | } else if (base !== null && (typeof base === "undefined" ? "undefined" : _typeof(base)) === "object") {
215 | // If finalize is called on an object that was not a proxy, it means that it is an object that was not there in the original
View compiled
produce
node_modules/immer/dist/immer.js:258
255 | //values either than undefined will trigger warning;
256 | !Object.is(maybeVoidReturn, undefined) && console.warn("Immer callback expects no return value. However " + (typeof maybeVoidReturn === "undefined" ? "undefined" : _typeof(maybeVoidReturn)) + " was returned");
257 | // and finalize the modified proxy
> 258 | var res = finalize(rootClone);
259 | // revoke all proxies
260 | revocableProxies.forEach(function (p) {
261 | return p.revoke();
View compiled
After the 'immer -> produce' change, produce uses classes and arrow functions in the es5 build, causing IE11 to have a fit :)
Protects people against accidentally using proxies in async fashion etc
Hi, I have flow 0.63.1 and while checking, I encountered this error
Error: node_modules/immer/dist/immer.js.flow:15
15: declare export default function<S>(
^ Unexpected token <
Do you know what might be the cause? Thanks!
$ ls node_modules/immer/
changelog.md dist LICENSE package.json readme.md
it worked fine with 0.6 or 0.7
Hi,
I am using typescript in my project. If Immer just uses native JS objects then it means that TS suggestions and compile errors should work right?
The title says it all, basically. When I tried to create an optimized production build of a project that uses immer with create-react-app
, I got told that immer.js
cannot be minified. I believe this is because you do not terminate your statements with semicolons - the first error was on line 10. Is there anything that can be done about this?
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.