Skip to content

Commit

Permalink
add reaction with effects
Browse files Browse the repository at this point in the history
  • Loading branch information
YousefED committed Nov 9, 2021
1 parent 1a39808 commit 462553a
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 15 deletions.
12 changes: 5 additions & 7 deletions packages/reactive-core/src/autorun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,20 @@ import { Reaction } from "./reaction";

export function autorun<T>(
func: () => T extends Promise<void> ? 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<T>(
func: (reactive: T) => Promise<void>,
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);
Expand Down
29 changes: 28 additions & 1 deletion packages/reactive-core/src/reaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,20 @@ import { Observer } from "./observer";

let runningReactions: Reaction[] = [];
export class Reaction extends Observer {
constructor(private func: () => void | Promise<void>, private options: { name: string }) {
private isInitial = true;

constructor(
private func: () => void | Promise<void>,
private options: { fireImmediately: boolean; name: string },
private effect?: () => void | Promise<void>
) {
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 = () => {
Expand All @@ -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() {
Expand All @@ -33,3 +50,13 @@ export function hasRunningReaction() {
export function runningReaction() {
return runningReactions.length ? runningReactions[runningReactions.length - 1] : undefined;
}

export function reaction(
func: () => any | Promise<any>,
effect: () => void | Promise<void>,
options?: { fireImmediately?: boolean; name?: string }
) {
const newOptions = { name: "unnamed", fireImmediately: true, ...options };
const r = new Reaction(func, newOptions, effect);
return r;
}
40 changes: 33 additions & 7 deletions packages/reactive-core/test/autorun.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);
});
});

Expand Down

0 comments on commit 462553a

Please sign in to comment.