-
-
Notifications
You must be signed in to change notification settings - Fork 852
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Immer does not work with a polyfilled Symbol implementation #577
Comments
Even polyfilled symbols should have stable identities, which is probably
why you fail to reproduce it in code sandbox. I suspect your issue is
rather that you have immer twice in your dependency tree, resulting in two
living definitions of those symbols.
…On Tue, 14 Apr 2020, 01:21 Tim, ***@***.***> wrote:
🐛 Bug Report
This bug only occurs when using the ES5 implementation (no proxies) and
there is a polyfilled version of Symbol on the window object (in my case
this is reproducible both with core-js
<https://github.com/zloirock/core-js> and es6-symbol
<https://github.com/medikoo/es6-symbol>). I already investigated and
found where the root cause is, though I am not sure what the best route
forward is (I have several suggestions -- see below)
Link to repro
Unfortunately, this is VERY hard to reproduce in codesandbox. I tried
importing polyfills in codesandbox and overwriting the native
implementation, but it was VERY finicky and I couldn't get it to work.
Hopefully the root cause explained below is enough to go on 😄
Root Cause
The issue occurs because there are checks within the immer codebase like
this:
if (key === DRAFT_STATE) {
// do something
}
One for instance, is in shallowCopy
<https://github.com/immerjs/immer/blob/5a968e8bc51d42e4b9024d47656d0d885613aac2/src/utils/common.ts#L153>.
In that case (and other similar ones), the draftState object is passed into
each where it will iterate through the keys (including the Symbol key
used to store draft state). Since Symbol is polyfilled, the draft state
key will be stringified and no longer pass the check for key ===
DRAFT_STATE, since the Symbol polyfill is not native and cannot be
attached to an object without being stringified. The native Symbol
produces objects with a special typeof === 'symbol whereas the polyfill
produces a plain object that can't follow the rules of Symbol. In the
case of shallowCopy, things blow up when the draft state key is not skipped
(like the comment says it should
<https://github.com/immerjs/immer/blob/5a968e8bc51d42e4b9024d47656d0d885613aac2/src/utils/common.ts#L154>
)
Potential Ideas
1. Change checks like this to be key == DRAFT_STATE
- Loose equals will work for when key is a string and DRAFT_STATE
is an object because DRAFT_STATE stringifies to the key value.
- Loose equals also works with the native implementation.
2. Change the check to be typeof DRAFT_STATE === 'symbol' ? key ===
DRAFT_STATE : key === DRAFTE_STATE.toString()
- Safely falls back to a string check if the DRAFT_STATE is not a
real symbol.
3. Change hasSymbol
<https://github.com/immerjs/immer/blob/0d87fc88e8efffdacbb5db295cb9efd624cd2757/src/utils/env.ts#L4>
to be const hasSymbol = typeof Symbol !== "undefined" && typeof
Symbol("test") === 'symbol'
- Esentially, only native Symbol definitions would be eligible.
- This way things like DRAFTABLE
<https://github.com/immerjs/immer/blob/0d87fc88e8efffdacbb5db295cb9efd624cd2757/src/utils/env.ts#L31>,
DRAFT_STATE
<https://github.com/immerjs/immer/blob/0d87fc88e8efffdacbb5db295cb9efd624cd2757/src/utils/env.ts#L35>,
and iteratorSymbol
<https://github.com/immerjs/immer/blob/0d87fc88e8efffdacbb5db295cb9efd624cd2757/src/utils/env.ts#L39>
all use the built in backups for when there is no Symbol in the
environment.
4. Add an export similar to setUseProxies called setUseSymbols.
To Reproduce
Environment must be using a Symbol polyfill
enableES5();
setUseProxies(false);
const baseState = {items: []};
const nextState = produce(baseState, (draftState) => {
draftState.items.push({id: "ID"});
});
Observed behavior
Exception is thrown:
scope.ts:81 Uncaught TypeError: Cannot read property 'type_' of undefined
at revokeDraft (scope.ts:81)
at Array.forEach (<anonymous>)
at revokeScope (scope.ts:63)
at processResult (finalize.ts:53)
at Immer.produce (immerClass.ts:117)
at <anonymous>:6:17
Expected behavior
No exception
Environment
- *Immer version:* Latest
- Occurs with setUseProxies(true)
- Occurs with setUseProxies(false) (ES5 only)
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#577>, or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBG4PZC2PO4CLTG7VZTRMOT6ZANCNFSM4MHKRM6A>
.
|
So after much effort, I was able to bend codesandbox to my will. I think there must be something going on there where ^ that only show cases the root problem. If you want to see the issue in action, I also created a repository to showcase it (the actual immer crash & core-js polyfill behavior): https://github.com/timothympace/immer-bug-reproduce If you download that ^, It comes down to the fact that the Symbol polyfill can't exist as a real Symbol key on objects. It get's stringified when it's used as an object key, which leads to what I mentioned in my first comment. |
In |
Gotcha! I finally understand the problem, sorry that it dawned so slowly and thanks for the repro! I think solution 1 is neat! Solution 3 would be even nicer, but I am not sure that is fully compatible with iterator protocols if the iterator symbol is created by some polyfill. Interested in creating a PR? |
Yeah I'd be happy to create a PR. Not sure I understand the potential problem with solution 3 though. Maybe I'm not fully understanding.... I'm suggesting to tighten the restrictions of Let me know which route you think is best for the project and I'll open a PR 😄 |
@timothympace so the concern here is, if we pick our own symbol (or none at all) for But with the same reasoning solution 3 can work, if we make sure to not make the check that strict for |
🎉 This issue has been resolved in version 6.0.8 🎉 The release is available on: Your semantic-release bot 📦🚀 |
* Introduced `current`, which takes a snapshot of the current state of a draft and finalizes it (but without freezing). Current is a great utility to print the current state during debugging (no Proxies in the way), and the output of current can also be safely leaked outside the producer. Implements #441, #591 * [BREAKING CHANGE] getters and setters are now handled consistently: own getters and setters will always by copied into fields (like Object.assign does), inherited getters and setters will be left as-is. This should allow using Immer directly on objects that trap their fields, like down in Vue or MobX. Fixes #584, #439, #593, #558 * [BREAKING CHANGE] produce no longer accepts non-draftable objects as first argument * [BREAKING CHANGE] original can only be called on drafts and will throw otherwise (fixes #605) * [BREAKING CHANGE] non-enumerable and symbolic fields will never be frozen * [BREAKING CHANGE] the patches for arrays are now computed differently to fix some scenarios in which they were incorrect. In some cases they will be more optimal now, in other cases less. Especially splicing / unshifting items into an existing array might result in a lot of patches. Fixes #468 * Improved documentation in several areas, there is now a page for typical update patterns and a separate page on how to work with classes. And additional performance tips have been included. Fixes #457, #115, #462 * Fixed #462: All branches of the produced state should be frozen * Fixed #588: Inconsistent behavior with nested produce * Fixed #577: Immer might not work with polyfilled symbols * Fixed #514, #609: Explicitly calling `useProxies(false)` shouldn’t check for the presence of Proxy.
🎉 This issue has been resolved in version 7.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
🐛 Bug Report
This bug only occurs when using the ES5 implementation (no proxies) and there is a polyfilled version of Symbol on the window object (in my case this is reproducible both with core-js and es6-symbol). I already investigated and found where the root cause is, though I am not sure what the best route forward is (I have several suggestions -- see below)
Link to repro
Unfortunately, this is VERY hard to reproduce in codesandbox. I tried importing polyfills in codesandbox and overwriting the native implementation, but it was VERY finicky and I couldn't get it to work. Hopefully the root cause explained below is enough to go on 😄
Root Cause
The issue occurs because there are checks within the immer codebase like this:
One for instance, is in
shallowCopy
. In that case (and other similar ones), the draftState object is passed intoeach
where it will iterate through the keys (including the Symbol key used to store draft state). SinceSymbol
is polyfilled, the draft state key will be stringified and no longer pass the check forkey === DRAFT_STATE
, since theSymbol
polyfill is not native and cannot be attached to an object without being stringified. The nativeSymbol
produces objects with a specialtypeof === 'symbol
whereas the polyfill produces a plainobject
that can't follow the rules ofSymbol
. In the case of shallowCopy, things blow up when the draft state key is not skipped (like the comment says it should)Potential Ideas
key == DRAFT_STATE
key
is a string andDRAFT_STATE
is an object becauseDRAFT_STATE
stringifies to the key value.typeof DRAFT_STATE === 'symbol' ? key === DRAFT_STATE : key === DRAFTE_STATE.toString()
DRAFT_STATE
is not a real symbol.hasSymbol
to beconst hasSymbol = typeof Symbol !== "undefined" && typeof Symbol("test") === 'symbol'
Symbol
definitions would be eligible.DRAFTABLE
,DRAFT_STATE
, anditeratorSymbol
all use the built in backups for when there is noSymbol
in the environment.setUseProxies
calledsetUseSymbols
.To Reproduce
Environment must be using a Symbol polyfill
Observed behavior
Exception is thrown:
Expected behavior
No exception
Environment
setUseProxies(true)
setUseProxies(false)
(ES5 only)The text was updated successfully, but these errors were encountered: