immer
immer copied to clipboard
Nested and chained produce usage results in error: Cannot perform 'get' on a proxy that has been revoked
🐛 Bug Report
When using nested and chained produce calls, when a property is copied from a child object to the parent, immer throws an error when accessing the property in the final state:
TypeError: Cannot perform 'get' on a proxy that has been revoked
Link to repro
PR with failing unit test is in https://github.com/immerjs/immer/pull/935
To Reproduce
The following example code shows this error:
const state = {
foo: {
bar: {
baz: 1
}
}
}
const newState = produce(state, draft => {
draft.foo = produce(draft.foo, fooDraft => {
fooDraft.baz = fooDraft.bar.baz
})
draft.foo = produce(draft.foo, fooDraft => {
/* another produce call makes this fail */
/* no actual mutation necessary to make this happen */
})
})
// Error is thrown here when the property is read
JSON.stringify(newState)
> TypeError: Cannot perform 'get' on a proxy that has been revoked
Observed behavior
An error is throw while reading the modified property
Expected behavior
No error to be throw.
Environment
Only seems to happen with autoFreeze = true.
- Immer version: Tested on v7.0.0 - v9.0.12 & 285fff927428291559505ec057512811c1951d10
- [x] I filed this report against the latest version of Immer
- [x] Occurs with
setUseProxies(true) - [x] Occurs with
setUseProxies(false)(ES5 only)
This seems related to https://github.com/immerjs/immer/issues/916, but the fix in https://github.com/immerjs/immer/pull/917 doesn't fix the case I've shown above.
Your test creates a non-unidirectional graph, which isn't supported by Immer. There should be only one single path from any node in your tree to the root. After the first assignment, foo.bar.bazs object lives at two different locations in the tree.
Thanks for the reply! In the real situation where I've encountered this issue, I'm not setting another direct reference to foo.bar.baz, I'm actually setting the result of a string.replace(). I've updated the test to reflect this real usage, and it still fails with the same issue. I had reduced the test case a little too much sorry.
The failing test is then:
const state = {
foo: {
bar: {
baz: "banana"
}
}
}
const newState = produce(state, draft => {
draft.foo = produce(draft.foo, fooDraft => {
fooDraft.baz = fooDraft.bar.baz.replace("banana", "apple")
})
draft.foo = produce(draft.foo, fooDraft => {
/* another produce call makes this fail */
/* no actual mutation necessary to make this happen */
})
})
JSON.stringify(newState)
I would expect newState to equal the following after this code is run, which doesn't reference the same object twice (at least not in vanilla JS, although I appreciate there may be references inside immer to make this all work!):
{
foo: {
bar: {
baz: "banana"
},
baz: "apple"
}
}
hey, dude, you can check this online example, it works very well. https://codesandbox.io/s/limu-case1-qyr9yu?file=/src/index.js

by the way: limu is a faster immutable lib, hope you like it.
Same thing happens with createDraft and finishDraft.
let state = createDraft({})
state.x = 10
let patches, inversePatches, nextState
nextState = finishDraft(state, (p, ip) => {
patches = p
inversePatches = ip
})
state = createDraft(nextState)
state.x = 20
// Throws
// TypeError: Cannot perform 'get' on a proxy that has been revoked
// as nextState is frozen by `setAutoFreeze`
Edit: The problem is not directly visible in that code, but trying to integrate this into a reactive store, we need keep snapshots or a pointer to the previous state. Is there a workaround to keep a pointer to state? Since isDraft(state) when finishDraft(state) will result in a proxy revoked.
Closing as original issue seems solved