Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add previous method #313

Merged
merged 6 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/babel-plugin-factories.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"patronum/once",
"patronum/or",
"patronum/pending",
"patronum/previous",
"patronum/reset",
"patronum/reshape",
"patronum/snapshot",
Expand Down Expand Up @@ -44,6 +45,7 @@
"once": "once",
"or": "or",
"pending": "pending",
"previous": "previous",
"reset": "reset",
"reshape": "reshape",
"snapshot": "snapshot",
Expand All @@ -54,4 +56,4 @@
"throttle": "throttle",
"time": "time"
}
}
}
1 change: 1 addition & 0 deletions src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ All methods split into categories.
- [snapshot](./snapshot/readme.md) — Create store value snapshot.
- [splitMap](./split-map/readme.md) — Split event to different events and map data.
- [spread](./spread/readme.md) — Send fields from object to same targets.
- [previous](./previous/readme.md) - Get previous value of store.

### Debug

Expand Down
34 changes: 34 additions & 0 deletions src/previous/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Node, Store, createStore, launch, step, is } from 'effector';

export function previous<State>(store: Store<State>): Store<State | null>;
export function previous<State, Init>(
store: Store<State>,
initialValue: Init,
): Store<State | Init>;
export function previous<State, Init = null>(
...args: [store: Store<State>, defaultValue?: Init]
) {
const [store] = args;
const initialValue = (args.length < 2 ? null : args[1]) as Init | null;
if (!is.store(store)) {
throw Error('previous first argument should be a store');
}
const $prevValue = createStore<State | Init | null>(initialValue, {
serialize: 'ignore',
skipVoid: false,
});
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;
}
87 changes: 87 additions & 0 deletions src/previous/previous.fork.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
allSettled,
createEvent,
createStore,
fork,
restore,
sample,
} from 'effector';

import { previous } from './index';

it('has null when store is not changed', () => {
const scope = fork();
const $initalStore = createStore(10);
const $prevValue = previous($initalStore);

expect(scope.getState($prevValue)).toBe(null);
});

it('has initial value when defined', () => {
const scope = fork();
const $initalStore = createStore(10);
const $prevValue = previous($initalStore, 0);

expect(scope.getState($prevValue)).toBe(0);
});

it('has first value on update', async () => {
const scope = fork();
const changeInitialStore = createEvent<number>();
const $initalStore = restore(changeInitialStore, 10);

const $prevValue = previous($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<number>();
const $initalStore = restore(changeInitialStore, 10);

const $prevValue = previous($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 = previous($initalStore, -1);
$initalStore.on(inc, (x) => x + 1);
const scope = fork({ values: [[$initalStore, 10]] });
await allSettled(inc, { scope });
expect(scope.getState($prevValue)).toBe(10);
});

test('undefined support', async () => {
const changeInitialStore = createEvent<string | void>();
const $initialStore = createStore<string | void>('a', { skipVoid: false });
const $prevValue = previous($initialStore);

sample({ clock: changeInitialStore, target: $initialStore });

const scope = fork({ values: [[$initialStore, 'b']] });
await allSettled(changeInitialStore, { scope, params: undefined });
expect(scope.getState($prevValue)).toBe('b');
await allSettled(changeInitialStore, { scope, params: 'c' });
expect(scope.getState($prevValue)).toBe(undefined);
});

test('undefined as defaultValue support', () => {
const changeInitialStore = createEvent<string | void>();
const $initialStore = createStore<string | void>('a', { skipVoid: false });
const $prevValue = previous($initialStore, undefined);

sample({ clock: changeInitialStore, target: $initialStore });

const scope = fork({ values: [[$initialStore, 'b']] });
expect(scope.getState($prevValue)).toBe(undefined);
});
73 changes: 73 additions & 0 deletions src/previous/previous.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { createEvent, createStore, restore, sample } from 'effector';

import { previous } from './index';

it('has null when store is not changed', () => {
const $initalStore = createStore(10);
const $prevValue = previous($initalStore);

expect($prevValue.getState()).toBe(null);
});

it('has initial value when defined', () => {
const $initalStore = createStore(10);
const $prevValue = previous($initalStore, 0);

expect($prevValue.getState()).toBe(0);
});

it('has first value on update', () => {
const changeInitialStore = createEvent<number>();
const $initalStore = restore(changeInitialStore, 10);

const $prevValue = previous($initalStore);

changeInitialStore(20);

expect($prevValue.getState()).toBe(10);
});

it('has previous value on multiple updates', () => {
const changeInitialStore = createEvent<number>();
const $initalStore = restore(changeInitialStore, 10);

const $prevValue = previous($initalStore);

changeInitialStore(20);
changeInitialStore(30);
changeInitialStore(40);

expect($prevValue.getState()).toBe(30);
});

test('undefined support', () => {
const changeInitialStore = createEvent<string | void>();
const $initialStore = createStore<string | void>('a', { skipVoid: false });
const $prevValue = previous($initialStore);

sample({ clock: changeInitialStore, target: $initialStore });

changeInitialStore();
expect($prevValue.getState()).toBe('a');
changeInitialStore('b');
expect($prevValue.getState()).toBe(undefined);
});

test('undefined as defaultValue support', () => {
const changeInitialStore = createEvent<string | void>();
const $initialStore = createStore<string | void>('a', { skipVoid: false });
const $prevValue = previous($initialStore, undefined);

sample({ clock: changeInitialStore, target: $initialStore });

expect($prevValue.getState()).toBe(undefined);
});

test('store validation', () => {
expect(() => {
// @ts-expect-error
previous(null);
}).toThrowErrorMatchingInlineSnapshot(
`"previous first argument should be a store"`,
);
});
65 changes: 65 additions & 0 deletions src/previous/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# previous

:::note since
patronum 2.1.0
:::

```ts
import { previous } from 'patronum';
// or
import { previous } from 'patronum/previous-value';
```

### Motivation

The method allows to get previous value of given store. Usually need for analytics

### Formulae

```ts
$target = previous($source);
$target = previous($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 { previous } from 'patronum';

const openNewRoute = createEvent<string>();
const $currentRoute = createStore('main_page');
const $previousRoute = previous($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
```
34 changes: 34 additions & 0 deletions test-typings/previous.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { expectType } from 'tsd';
import { Store, createStore, createEvent } from 'effector';
import { previous } from '../dist/previous';

{
const $foo = createStore('a');
const $fooPrev = previous($foo);

expectType<Store<string | null>>($fooPrev);
}
{
const $foo = createStore('a');
const $fooPrev = previous($foo, 'b');

expectType<Store<string>>($fooPrev);
}
{
const $foo = createStore('a');
const $fooPrev = previous($foo, 0);

expectType<Store<string | number>>($fooPrev);
}
{
const $foo = createStore('a');
const $fooPrev = previous($foo, undefined);

expectType<Store<string | void>>($fooPrev);
}
{
const foo = createEvent();

// @ts-expect-error
previous(foo, 0);
}
Loading