Skip to content

Commit

Permalink
have transformer not memorize when called outside reactive context
Browse files Browse the repository at this point in the history
  • Loading branch information
upsuper committed Nov 29, 2019
1 parent c2f9b42 commit 887d928
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 15 deletions.
45 changes: 30 additions & 15 deletions 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<A, B> = (object: A) => B

export interface ITransformerParams<A, B> {
onCleanup?: (resultObject: B | undefined, sourceObject?: A) => void
keepAlive?: boolean
debugNameGenerator?: (sourceObject?: A) => string
}

Expand Down Expand Up @@ -36,19 +37,18 @@ export function createTransformer<A, B>(
// Memoizes: object id -> reactive view that applies transformer to the object
let views: { [id: number]: IComputedValue<B> } = {}
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-${(<any>transformer).name}-${sourceIdentifier}`
Expand All @@ -60,18 +60,33 @@ export function createTransformer<A, B>(
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()
Expand Down
18 changes: 18 additions & 0 deletions test/create-transformer.ts
Expand Up @@ -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()
})

0 comments on commit 887d928

Please sign in to comment.