diff --git a/src/create-transformer.ts b/src/create-transformer.ts index 7b6dccb..6e8b1cb 100644 --- a/src/create-transformer.ts +++ b/src/create-transformer.ts @@ -1,10 +1,11 @@ -import { computed, onBecomeUnobserved, IComputedValue } from "mobx" +import { computed, onBecomeUnobserved, IComputedValue, _isComputingDerivation } from "mobx" import { invariant, addHiddenProp } from "./utils" export type ITransformer = (object: A) => B export interface ITransformerParams { onCleanup?: (resultObject: B | undefined, sourceObject?: A) => void + keepAlive?: boolean debugNameGenerator?: (sourceObject?: A) => string } @@ -36,19 +37,18 @@ export function createTransformer( // Memoizes: object id -> reactive view that applies transformer to the object let views: { [id: number]: IComputedValue } = {} let onCleanup: Function = undefined + let keepAlive: boolean = false let debugNameGenerator: Function = undefined + if (typeof arg2 === "object") { + onCleanup = arg2.onCleanup + keepAlive = arg2.keepAlive !== undefined ? arg2.keepAlive : false + debugNameGenerator = arg2.debugNameGenerator + } else if (typeof arg2 === "function") { + onCleanup = arg2 + } function createView(sourceIdentifier: number, sourceObject: A) { let latestValue: B - if (typeof arg2 === "object") { - onCleanup = arg2.onCleanup - debugNameGenerator = arg2.debugNameGenerator - } else if (typeof arg2 === "function") { - onCleanup = arg2 - } else { - onCleanup = undefined - debugNameGenerator = undefined - } const prettifiedName = debugNameGenerator ? debugNameGenerator(sourceObject) : `Transformer-${(transformer).name}-${sourceIdentifier}` @@ -60,18 +60,33 @@ export function createTransformer( name: prettifiedName } ) - const disposer = onBecomeUnobserved(expr, () => { - delete views[sourceIdentifier] - disposer() - if (onCleanup) onCleanup(latestValue, sourceObject) - }) + if (!keepAlive) { + const disposer = onBecomeUnobserved(expr, () => { + delete views[sourceIdentifier] + disposer() + if (onCleanup) onCleanup(latestValue, sourceObject) + }) + } return expr } + let memoWarned = false return (object: A) => { const identifier = getMemoizationId(object) let reactiveView = views[identifier] if (reactiveView) return reactiveView.get() + if (!keepAlive && !_isComputingDerivation()) { + if (!memoWarned) { + console.warn( + "invoking a transformer from outside a reactive context won't memorized " + + "and is cleaned up immediately, unless keepAlive is set" + ) + memoWarned = true + } + const value = transformer(object) + if (onCleanup) onCleanup(value, object) + return value + } // Not in cache; create a reactive view reactiveView = views[identifier] = createView(identifier, object) return reactiveView.get() diff --git a/test/create-transformer.ts b/test/create-transformer.ts index b50c835..23f0740 100644 --- a/test/create-transformer.ts +++ b/test/create-transformer.ts @@ -1354,3 +1354,21 @@ test("should respect debugNameGenerator argument", () => { objectName = m.getObserverTree(state, "title").observers[0].name expect(objectName).toBe("COFFEE-DEBUG") }) + +test("should not memorize outside reactive context", () => { + let transformer = jest.fn((input: string) => input.toLowerCase()) + let onCleanup = jest.fn() + let stubTransformer = createTransformer(transformer, onCleanup) + + stubTransformer("HELLO") + expect(transformer).toHaveBeenCalledWith("HELLO") + expect(onCleanup).toHaveBeenCalledWith("hello", "HELLO") + transformer.mockClear() + onCleanup.mockClear() + + stubTransformer("HELLO") + expect(transformer).toHaveBeenCalledWith("HELLO") + expect(onCleanup).toHaveBeenCalledWith("hello", "HELLO") + transformer.mockClear() + onCleanup.mockClear() +})