Skip to content

Commit

Permalink
feat: add hooks to the Immer class
Browse files Browse the repository at this point in the history
Hooks are passed as options to the Immer class.

onAssign(state, prop, value)
  Called for every property assigned to by a producer

onDelete(state, prop)
  Called for every property deleted by a producer

onCopy(state)
  Called whenever a copy becomes finalized

These hooks are passed an `ImmerState` object. (defined at the end of "src/index.d.ts")

The `state` is guaranteed to be both modified and finalized if ever passed to a hook.
  • Loading branch information
aleclarson committed Dec 15, 2018
1 parent f36d329 commit 7f50364
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 14 deletions.
50 changes: 37 additions & 13 deletions src/immer.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ const configDefaults = {
autoFreeze:
typeof process !== "undefined"
? process.env.NODE_ENV !== "production"
: verifyMinified.name === "verifyMinified"
: verifyMinified.name === "verifyMinified",
onAssign: null,
onDelete: null,
onCopy: null
}

export class Immer {
Expand Down Expand Up @@ -131,6 +134,12 @@ export class Immer {
if (!state.finalized) {
state.finalized = true
this.finalizeTree(state.draft, path, patches, inversePatches)
if (this.onDelete) {
const {assigned} = state
for (const prop in assigned)
assigned[prop] || this.onDelete(state, prop)
}
if (this.onCopy) this.onCopy(state)
if (this.autoFreeze) Object.freeze(state.copy)
if (patches) generatePatches(state, path, patches, inversePatches)
}
Expand All @@ -147,20 +156,35 @@ export class Immer {
? state.copy
: (state.copy = shallowCopy(state.draft))
}
const {onAssign} = this
const finalizeProperty = (prop, value, parent) => {
// Skip unchanged properties in draft objects.
if (state && parent === root && is(value, state.base[prop])) return
if (!isDraftable(value)) return
if (!isDraft(value)) {
// Frozen values are already finalized.
return Object.isFrozen(value) || each(value, finalizeProperty)
// Only `root` can be a draft in here.
const inDraft = state && parent === root
if (isDraftable(value)) {
// Skip unchanged properties in draft objects.
if (inDraft && is(value, state.base[prop])) return

// Find proxies within new objects. Frozen objects are already finalized.
if (!isDraft(value)) {
if (!Object.isFrozen(value)) each(value, finalizeProperty)
if (onAssign && inDraft) onAssign(state, prop, value)
return
}

// prettier-ignore
parent[prop] =
// Patches are never generated for assigned properties.
patches && parent === root && !(state && has(state.assigned, prop))
? this.finalize(value, path.concat(prop), patches, inversePatches)
: this.finalize(value)

if (onAssign && inDraft) onAssign(state, prop, parent[prop])
return
}
// Call `onAssign` for primitive values.
if (onAssign && inDraft && !is(value, state.base[prop])) {
onAssign(state, prop, value)
}
// prettier-ignore
parent[prop] =
// Patches are never generated for assigned properties.
patches && parent === root && !(state && has(state.assigned, prop))
? this.finalize(value, path.concat(prop), patches, inversePatches)
: this.finalize(value)
}
each(root, finalizeProperty)
return root
Expand Down
15 changes: 14 additions & 1 deletion src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,13 @@ export function original<T>(value: T): T | void
export function isDraft(value: any): boolean

export class Immer {
constructor(config: {useProxies?: boolean; autoFreeze?: boolean})
constructor(config: {
useProxies?: boolean
autoFreeze?: boolean
onAssign?: (state: ImmerState, prop: keyof any, value: any) => void
onDelete?: (state: ImmerState, prop: keyof any) => void
onCopy?: (state: ImmerState) => void
})
/**
* The `produce` function takes a value and a "recipe function" (whose
* return value often depends on the base state). The recipe function is
Expand Down Expand Up @@ -173,3 +179,10 @@ export class Immer {
*/
setUseProxies(useProxies: boolean): void
}

export interface ImmerState<T = any> {
parent?: ImmerState
base: T
copy: T
assigned: {[prop: string]: boolean}
}

0 comments on commit 7f50364

Please sign in to comment.