Skip to content

Commit

Permalink
fix: recursive produce call in async recipe
Browse files Browse the repository at this point in the history
  • Loading branch information
aleclarson committed Jan 31, 2019
1 parent c5e6ec4 commit cd7d673
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 18 deletions.
40 changes: 22 additions & 18 deletions src/immer.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,9 @@ export class Immer {
this.onCopy(state)
}

// Nested producers must never auto-freeze their result,
// because it may contain drafts from parent producers.
if (this.autoFreeze && !scope.parent) {
// At this point, all descendants of `state.copy` have been finalized,
// so we can be sure that `scope.canAutoFreeze` is accurate.
if (this.autoFreeze && scope.canAutoFreeze) {
Object.freeze(state.copy)
}

Expand All @@ -196,7 +196,7 @@ export class Immer {
* @internal
* Finalize all drafts in the given state tree.
*/
finalizeTree(root, path, scope) {
finalizeTree(root, rootPath, scope) {
const state = root[DRAFT_STATE]
if (state) {
if (!this.useProxies) {
Expand All @@ -207,24 +207,28 @@ export class Immer {
root = state.copy
}

const {onAssign} = this
const needPatches = !!rootPath && !!scope.patches
const finalizeProperty = (prop, value, parent) => {
if (value === parent) {
throw Error("Immer forbids circular references")
}

// The only possible draft (in the scope of a `finalizeTree` call) is the `root` object.
const inDraft = !!state && parent === root
// In the `finalizeTree` method, only the `root` object may be a draft.
const isDraftProp = !!state && parent === root

if (isDraft(value)) {
const needPatches =
inDraft && path && scope.patches && !state.assigned[prop]
const path =
isDraftProp && needPatches && !state.assigned[prop]
? rootPath.concat(prop)
: null

value = this.finalize(
value,
needPatches ? path.concat(prop) : null,
scope
)
// Drafts owned by `scope` are finalized here.
value = this.finalize(value, path, scope)

// Drafts from another scope must prevent auto-freezing.
if (isDraft(value)) {
scope.canAutoFreeze = false
}

// Preserve non-enumerable properties.
if (Array.isArray(parent) || isEnumerable(parent, prop)) {
Expand All @@ -234,19 +238,19 @@ export class Immer {
}

// Unchanged drafts are never passed to the `onAssign` hook.
if (inDraft && value === state.base[prop]) return
if (isDraftProp && value === state.base[prop]) return
}
// Unchanged draft properties are ignored.
else if (inDraft && is(value, state.base[prop])) {
else if (isDraftProp && is(value, state.base[prop])) {
return
}
// Search new objects for unfinalized drafts. Frozen objects should never contain drafts.
else if (isDraftable(value) && !Object.isFrozen(value)) {
each(value, finalizeProperty)
}

if (inDraft && onAssign) {
onAssign(state, prop, value)
if (isDraftProp && this.onAssign) {
this.onAssign(state, prop, value)
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export class ImmerScope {
this.drafts = []
this.parent = parent

// Whenever the modified draft contains a draft from another scope, we
// need to prevent auto-freezing so the unowned draft can be finalized.
this.canAutoFreeze = true

// To avoid prototype lookups:
this.patches = null
}
Expand Down

0 comments on commit cd7d673

Please sign in to comment.