diff --git a/__tests__/flow/flow.js.flow b/__tests__/flow/flow.js.flow index 402b90f5..02f79ee7 100644 --- a/__tests__/flow/flow.js.flow +++ b/__tests__/flow/flow.js.flow @@ -1,5 +1,5 @@ // @flow -import produce, {setAutoFreeze, setUseProxies, produce as produce2, applyPatches, Patch} from "../../src/immer" +import produce, {setAutoFreeze, setUseProxies, produce as produce2, applyPatches, Patch, original} from "../../src/immer" setAutoFreeze(true) setUseProxies(true) @@ -93,4 +93,26 @@ produce({x: 3}, []) p = patches }) applyPatches({}, p) +} + +{ + produce({x: 3}, draftState => { + let a = original(draftState) + if (a) { + a.x + // $ExpectError + a.y + } + }) +} + +{ + produce([1], draftState => { + const a = original(draftState); + if (a) { + const b: number = a[0]; + // $ExpectError + const c: string = a[0]; + } + }) } \ No newline at end of file diff --git a/__tests__/flow/ts.ts b/__tests__/flow/ts.ts index be3ad99c..a584cf05 100644 --- a/__tests__/flow/ts.ts +++ b/__tests__/flow/ts.ts @@ -1,4 +1,4 @@ -import produce, {setAutoFreeze, setUseProxies} from "../../src/immer" +import produce, {setAutoFreeze, setUseProxies, original} from "../../src/immer" setAutoFreeze(true) setUseProxies(true) @@ -77,3 +77,22 @@ produce({x: 3}, []) ? produce(handlers[type])(state, payload) : state } + +produce({x: 3, z: {}}, draftState => { + const a = draftState; + + if (a) { + a.x + // $ExpectError + a.y + } +}) + +produce([1], draftState => { + const a = original(draftState); + if (a) { + // $ExpectError + const b: string = a[0]; + const c: number = a[0]; + } +}) diff --git a/__tests__/original.js b/__tests__/original.js new file mode 100644 index 00000000..39d0f18e --- /dev/null +++ b/__tests__/original.js @@ -0,0 +1,49 @@ +"use strict" +import produce, {original, setUseProxies} from "../src/immer" + +describe("original", () => { + const baseState = { + a: [], + b: {} + } + + it("should return the original from the draft", () => { + setUseProxies(true) + + produce(baseState, draftState => { + expect(original(draftState)).toBe(baseState) + expect(original(draftState.a)).toBe(baseState.a) + expect(original(draftState.b)).toBe(baseState.b) + }) + + setUseProxies(false) + + produce(baseState, draftState => { + expect(original(draftState)).toBe(baseState) + expect(original(draftState.a)).toBe(baseState.a) + expect(original(draftState.b)).toBe(baseState.b) + }) + }) + + it("should return the original from the proxy", () => { + produce(baseState, draftState => { + expect(original(draftState)).toBe(baseState) + expect(original(draftState.a)).toBe(baseState.a) + expect(original(draftState.b)).toBe(baseState.b) + }) + }) + + it("should return undefined for new values on the draft", () => { + produce(baseState, draftState => { + draftState.c = {} + draftState.d = 3 + expect(original(draftState.c)).toBeUndefined() + expect(original(draftState.d)).toBeUndefined() + }) + }) + + it("should return undefined for an object that is not proxied", () => { + expect(original({})).toBeUndefined() + expect(original(3)).toBeUndefined() + }) +}) diff --git a/readme.md b/readme.md index 3e8ea163..5036becf 100644 --- a/readme.md +++ b/readme.md @@ -351,6 +351,17 @@ const userReducer = produce((draft, action) => { }) ``` +## Extracting the original object from a proxied instance + +Immer exposes a named export `original` that will get the original object from the proxied instance inside `produce` (or return `undefined` for unproxied values). A good example of when this can be useful is when searching for nodes in a tree-like state using strict equality. + +```js +const baseState = { users: [{ name: "Richie" }] }; +const nextState = produce(baseState, draftState => { + original(draftState.users) // is === baseState.users +}) +``` + ## Using `this` The recipe will be always invoked with the `draft` as `this` context. diff --git a/src/common.js b/src/common.js index 4c13d733..5da97dda 100644 --- a/src/common.js +++ b/src/common.js @@ -56,6 +56,13 @@ export function freeze(value) { return value } +export function original(value) { + if (value && value[PROXY_STATE]) { + return value[PROXY_STATE].base + } + // otherwise return undefined +} + const assign = Object.assign || function assign(target, value) { diff --git a/src/immer.d.ts b/src/immer.d.ts index d5e78b80..860dfbe3 100644 --- a/src/immer.d.ts +++ b/src/immer.d.ts @@ -109,4 +109,6 @@ export function setAutoFreeze(autoFreeze: boolean): void */ export function setUseProxies(useProxies: boolean): void -export function applyPatches(state: S, patches: Patch[]): S \ No newline at end of file +export function applyPatches(state: S, patches: Patch[]): S + +export function original(value: T): T | void diff --git a/src/immer.js b/src/immer.js index 8f35ef80..c90146c8 100644 --- a/src/immer.js +++ b/src/immer.js @@ -1,4 +1,4 @@ -export {setAutoFreeze, setUseProxies} from "./common" +export {setAutoFreeze, setUseProxies, original} from "./common" import {applyPatches as applyPatchesImpl} from "./patches" import {isProxyable, getUseProxies} from "./common" diff --git a/src/immer.js.flow b/src/immer.js.flow index 905c2ac1..284cbfb1 100644 --- a/src/immer.js.flow +++ b/src/immer.js.flow @@ -83,4 +83,6 @@ declare export function setAutoFreeze(autoFreeze: boolean): void */ declare export function setUseProxies(useProxies: boolean): void -declare export function applyPatches(state: S, patches: Patch[]): S \ No newline at end of file +declare export function applyPatches(state: S, patches: Patch[]): S + +declare export function original(value: S): ?S \ No newline at end of file