diff --git a/packages/reactive-core/src/autorun.ts b/packages/reactive-core/src/autorun.ts index f162052..2b7e93f 100644 --- a/packages/reactive-core/src/autorun.ts +++ b/packages/reactive-core/src/autorun.ts @@ -3,22 +3,20 @@ import { Reaction } from "./reaction"; export function autorun( func: () => T extends Promise ? never : T extends void ? T : never, - extraOptions?: { fireImmediately?: boolean; name?: string } + extraOptions?: { name?: string } ): Reaction { - const options = Object.assign({ fireImmediately: true, name: "unnamed" }, extraOptions); + const options = { name: "unnamed", fireImmediately: true, ...extraOptions }; const reaction = new Reaction(func, options); - if (options.fireImmediately) { - reaction.trigger(); - } + return reaction; } export function autorunAsync( func: (reactive: T) => Promise, reactiveObject: T, - extraOptions?: { fireImmediately?: boolean; name?: string } + extraOptions?: { name?: string } ): Reaction { - const options = Object.assign({ fireImmediately: true, name: "unnamed" }, extraOptions); + const options = { name: "unnamed", fireImmediately: true, ...extraOptions }; const reaction = new Reaction(() => { func(reactiveObject); // TODO: error handling }, options); diff --git a/packages/reactive-core/src/reaction.ts b/packages/reactive-core/src/reaction.ts index 2202763..5fe1f65 100644 --- a/packages/reactive-core/src/reaction.ts +++ b/packages/reactive-core/src/reaction.ts @@ -2,8 +2,20 @@ import { Observer } from "./observer"; let runningReactions: Reaction[] = []; export class Reaction extends Observer { - constructor(private func: () => void | Promise, private options: { name: string }) { + private isInitial = true; + + constructor( + private func: () => void | Promise, + private options: { fireImmediately: boolean; name: string }, + private effect?: () => void | Promise + ) { super(() => this._trigger()); + + if (!effect && !this.options.fireImmediately) { + throw new Error("if no effect function passed, should always fireImmediately"); + } + // fire reaction + this.reaction(); } private reaction = () => { @@ -14,6 +26,11 @@ export class Reaction extends Observer { } finally { runningReactions.pop(); } + + if (this.effect && (!this.isInitial || this.options.fireImmediately)) { + this.effect(); + } + this.isInitial = false; }; private _trigger() { @@ -33,3 +50,13 @@ export function hasRunningReaction() { export function runningReaction() { return runningReactions.length ? runningReactions[runningReactions.length - 1] : undefined; } + +export function reaction( + func: () => any | Promise, + effect: () => void | Promise, + options?: { fireImmediately?: boolean; name?: string } +) { + const newOptions = { name: "unnamed", fireImmediately: true, ...options }; + const r = new Reaction(func, newOptions, effect); + return r; +} diff --git a/packages/reactive-core/test/autorun.test.ts b/packages/reactive-core/test/autorun.test.ts index 6354db8..2b6cce1 100644 --- a/packages/reactive-core/test/autorun.test.ts +++ b/packages/reactive-core/test/autorun.test.ts @@ -1,4 +1,4 @@ -import { runInAction } from "@reactivedata/reactive"; +import { reaction, runInAction } from "@reactivedata/reactive"; import { autorun, autorunAsync } from "../src/autorun"; import { reactive } from "../src/observable"; import { Observer } from "../src/observer"; @@ -592,15 +592,41 @@ describe("autorun", () => { describe("options", () => { describe("fireImmediately", () => { it("should not run the passed function, if set to true", () => { - const fnSpy = jest.fn(() => {}); - autorun(fnSpy, { fireImmediately: false }); - expect(fnSpy).toBeCalledTimes(0); + const nums = reactive({ num1: 0, num2: 1, num3: 2 }); + + const fnSpyTrigger = jest.fn(() => { + let x = nums.num1; + }); + const fnSpyEffect = jest.fn(() => { + let x = nums.num1; + }); + reaction(fnSpyTrigger, fnSpyEffect, { fireImmediately: false }); + expect(fnSpyTrigger).toBeCalledTimes(1); + expect(fnSpyEffect).toBeCalledTimes(0); + + nums.num1++; + + expect(fnSpyTrigger).toBeCalledTimes(2); + expect(fnSpyEffect).toBeCalledTimes(1); }); it("should default to true", () => { - const fnSpy = jest.fn(() => {}); - autorun(fnSpy); - expect(fnSpy).toHaveBeenCalledTimes(1); + const nums = reactive({ num1: 0, num2: 1, num3: 2 }); + + const fnSpyTrigger = jest.fn(() => { + let x = nums.num1; + }); + const fnSpyEffect = jest.fn(() => { + let x = nums.num1; + }); + reaction(fnSpyTrigger, fnSpyEffect); + expect(fnSpyTrigger).toBeCalledTimes(1); + expect(fnSpyEffect).toBeCalledTimes(1); + + nums.num1++; + + expect(fnSpyTrigger).toBeCalledTimes(2); + expect(fnSpyEffect).toBeCalledTimes(2); }); });