diff --git a/src/babel-plugin-factories.json b/src/babel-plugin-factories.json index 8fc1e510..0128aa52 100644 --- a/src/babel-plugin-factories.json +++ b/src/babel-plugin-factories.json @@ -17,6 +17,7 @@ "patronum/once", "patronum/or", "patronum/pending", + "patronum/previous-value", "patronum/reset", "patronum/reshape", "patronum/snapshot", @@ -44,6 +45,7 @@ "once": "once", "or": "or", "pending": "pending", + "previousValue": "previous-value", "reset": "reset", "reshape": "reshape", "snapshot": "snapshot", @@ -54,4 +56,4 @@ "throttle": "throttle", "time": "time" } -} \ No newline at end of file +} diff --git a/src/previous-value/index.ts b/src/previous-value/index.ts new file mode 100644 index 00000000..aa6e8b78 --- /dev/null +++ b/src/previous-value/index.ts @@ -0,0 +1,27 @@ +import { Node, Store, createStore, launch, step } from 'effector'; + +export function previousValue(store: Store): Store; +export function previousValue( + store: Store, + initialValue: Init, +): Store; +export function previousValue( + store: Store, + initialValue: Init | null = null, +) { + const $prevValue = createStore(initialValue); + const storeNode: Node = (store as any).graphite; + storeNode.seq.push( + step.compute({ + fn(upd, _, stack) { + launch({ + target: $prevValue, + params: stack.a, + defer: true, + }); + return upd; + }, + }), + ); + return $prevValue; +} diff --git a/src/previous-value/previous-value.fork.test.ts b/src/previous-value/previous-value.fork.test.ts new file mode 100644 index 00000000..9bf30610 --- /dev/null +++ b/src/previous-value/previous-value.fork.test.ts @@ -0,0 +1,55 @@ +import { allSettled, createEvent, createStore, fork, restore } from 'effector'; + +import { previousValue } from './index'; + +it('has null when store is not changed', () => { + const scope = fork(); + const $initalStore = createStore(10); + const $prevValue = previousValue($initalStore); + + expect(scope.getState($prevValue)).toBe(null); +}); + +it('has initial value when defined', () => { + const scope = fork(); + const $initalStore = createStore(10); + const $prevValue = previousValue($initalStore, 0); + + expect(scope.getState($prevValue)).toBe(0); +}); + +it('has first value on update', async () => { + const scope = fork(); + const changeInitialStore = createEvent(); + const $initalStore = restore(changeInitialStore, 10); + + const $prevValue = previousValue($initalStore); + + await allSettled(changeInitialStore, { scope, params: 20 }); + + expect(scope.getState($prevValue)).toBe(10); +}); + +it('has previous value on multiple updates', async () => { + const scope = fork(); + const changeInitialStore = createEvent(); + const $initalStore = restore(changeInitialStore, 10); + + const $prevValue = previousValue($initalStore); + + await allSettled(changeInitialStore, { scope, params: 20 }); + await allSettled(changeInitialStore, { scope, params: 30 }); + await allSettled(changeInitialStore, { scope, params: 40 }); + + expect(scope.getState($prevValue)).toBe(30); +}); + +it('has first scope value after first update', async () => { + const inc = createEvent(); + const $initalStore = createStore(0); + const $prevValue = previousValue($initalStore, -1); + $initalStore.on(inc, (x) => x + 1); + const scope = fork({ values: [[$initalStore, 10]] }); + await allSettled(inc, { scope }); + expect(scope.getState($prevValue)).toBe(10); +}); diff --git a/src/previous-value/previous-value.test.ts b/src/previous-value/previous-value.test.ts new file mode 100644 index 00000000..a37e4537 --- /dev/null +++ b/src/previous-value/previous-value.test.ts @@ -0,0 +1,41 @@ +import { createEvent, createStore, restore } from 'effector'; + +import { previousValue } from './index'; + +it('has null when store is not changed', () => { + const $initalStore = createStore(10); + const $prevValue = previousValue($initalStore); + + expect($prevValue.getState()).toBe(null); +}); + +it('has initial value when defined', () => { + const $initalStore = createStore(10); + const $prevValue = previousValue($initalStore, 0); + + expect($prevValue.getState()).toBe(0); +}); + +it('has first value on update', () => { + const changeInitialStore = createEvent(); + const $initalStore = restore(changeInitialStore, 10); + + const $prevValue = previousValue($initalStore); + + changeInitialStore(20); + + expect($prevValue.getState()).toBe(10); +}); + +it('has previous value on multiple updates', () => { + const changeInitialStore = createEvent(); + const $initalStore = restore(changeInitialStore, 10); + + const $prevValue = previousValue($initalStore); + + changeInitialStore(20); + changeInitialStore(30); + changeInitialStore(40); + + expect($prevValue.getState()).toBe(30); +}); diff --git a/src/previous-value/readme.md b/src/previous-value/readme.md new file mode 100644 index 00000000..0d968928 --- /dev/null +++ b/src/previous-value/readme.md @@ -0,0 +1,65 @@ +# previousValue + +:::note since +patronum 2.1.0 +::: + +```ts +import { previousValue } from 'patronum'; +// or +import { previousValue } from 'patronum/previous-value'; +``` + +### Motivation + +The method allows to get previous value of given store. Usually need for analytics + +### Formulae + +```ts +$target = previousValue($source); +$target = previousValue($source, 'initial value'); +``` + +### Arguments + +1. `$source` ([_`Store`_]) - source store +2. `defaultValue` (_optional_) - default value for `$target` store, if not passed, `null` will be used + +### Returns + +- `$target` ([_`Store`_]) - new store that contain previous value of `$source` after first update and null or default value (if passed) before that + +### Example + +Push analytics with route transition: + +```ts +import { createStore, createEvent, createEffect, sample } from 'effector'; +import { previousValue } from 'patronum'; + +const openNewRoute = createEvent(); +const $currentRoute = createStore('main_page'); +const $previousRoute = previousValue($currentRoute); + +const sendRouteTransitionFx = createEffect(async ({ prevRoute, nextRoute }) => { + console.log(prevRoute, '->', newRoute) + await fetch(...) +}); + +sample({clock: openNewRoute, target: $currentRoute}); + +sample({ + clock: openNewRoute, + source: { + prevRoute: $previousRoute, + nextRoute: $currentRoute, + }, + target: sendRouteTransitionFx, +}); + +openNewRoute('messages'); +// main_page -> messages +openNewRoute('chats'); +// messages -> chats +```