Skip to content

Commit

Permalink
Add spread(targets) shorthand
Browse files Browse the repository at this point in the history
  • Loading branch information
zerobias committed Dec 15, 2023
1 parent d506bc7 commit de23027
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 101 deletions.
57 changes: 46 additions & 11 deletions src/spread/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { createEvent, Event, EventCallable, sample, Unit, UnitTargetable } from 'effector';
import {
createEvent,
EventCallable,
is,
sample,
Unit,
UnitTargetable,
} from 'effector';

const hasPropBase = {}.hasOwnProperty;
const hasOwnProp = <O extends { [k: string]: unknown }>(object: O, key: string) =>
hasPropBase.call(object, key);

type NoInfer<T> = [T][T extends any ? 0 : never];
type EventAsReturnType<Payload> = any extends Payload ? Event<Payload> : never;

export function spread<Payload>(config: {
targets: {
Expand All @@ -25,22 +31,31 @@ export function spread<
};
}): Source;

export function spread<Payload>(targets: {
[Key in keyof Payload]?: UnitTargetable<Payload[Key]>;
}): EventCallable<Partial<Payload>>;

/**
* @example
* spread({ source: dataObject, targets: { first: targetA, second: targetB } })
* sample({
* target: spread({targets: { first: targetA, second: targetB } })
* })
*/
export function spread<P>({
targets,
source = createEvent<P>(),
}: {
targets: {
[Key in keyof P]?: Unit<P[Key]>;
};
source?: Unit<P>;
}): EventCallable<P> {
export function spread<P>(
args:
| {
targets: {
[Key in keyof P]?: Unit<P[Key]>;
};
source?: Unit<P>;
}
| {
[Key in keyof P]?: Unit<P[Key]>;
},
): EventCallable<P> {
const argsShape = isTargets(args) ? { targets: args } : args;
const { targets, source = createEvent<P>() } = argsShape;
for (const targetKey in targets) {
if (hasOwnProp(targets, targetKey)) {
const currentTarget = targets[targetKey];
Expand All @@ -63,3 +78,23 @@ export function spread<P>({

return source as any;
}

function isTargets<P>(
args:
| {
targets: {
[Key in keyof P]?: Unit<P[Key]>;
};
source?: Unit<P>;
}
| {
[Key in keyof P]?: Unit<P[Key]>;
},
): args is {
[Key in keyof P]?: Unit<P[Key]>;
} {
return Object.keys(args).some(
(key) =>
!['targets', 'source'].includes(key) && is.unit(args[key as keyof typeof args]),
);
}
175 changes: 98 additions & 77 deletions src/spread/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,101 @@ import { spread } from 'patronum';
import { spread } from 'patronum/spread';
```

## `source = spread(targets)`

:::note since
patronum 2.1.0
Use `spread({ targets })` with patronum < 2.1.0
:::

### Motivation

This method allows to trigger many target at once, if they match the source structure.
It is useful when you need to destructure object and save values to different stores.

### Formulae

```ts
source = spread({ field: target, ... })
```

- When `source` is triggered with **object**, extract `field` from data, and trigger `target`
- `targets` can have multiple properties
- If the `source` was triggered with non-object, nothing would be happening
- If `source` is triggered with object but without propertpy `field`, target for this `field` will not be triggered

### Arguments

1. `targets` `(Record<string, Event<T> | Store<T> | Effect<T>>)` — Flat object which key is key in `source` payload, and value is unit to store value to.

### Returns

- `source` `(Event<T>)` — Source event, data passed to it should be an object with fields from `targets`

### Example

#### Conditionally save value to stores

```ts
import { createStore, createEvent, sample } from 'effector';
import { spread } from 'patronum';

const $first = createStore('');
const $last = createStore('');

const formReceived = createEvent();

sample({
source: formReceived,
filter: (form) => form.first.length > 0 && form.last.length > 0,
target: spread({
first: $first,
last: $last,
}),
});

$first.watch((first) => console.log('First name', first));
$last.watch((last) => console.log('Last name', last));

formReceived({ first: '', last: '' });
// Nothing, because filter returned true

formReceived({ first: 'Hello', last: 'World' });
// => First name Hello
// => Last name World
```

#### Nested spreading

```ts
const $targetA = createStore('');
const $targetB = createStore(0);
const $targetC = createStore(false);

const trigger = spread({
first: $targetA,
second: spread({
foo: $targetB,
bar: $targetC,
}),
});

$targetA.watch((payload) => console.log('targetA', payload));
$targetB.watch((payload) => console.log('targetB', payload));
$targetC.watch((payload) => console.log('targetC', payload));

trigger({
first: 'Hello',
second: {
foo: 200,
bar: true,
},
});
// => targetA Hello
// => targetB 200
// => targetC true
```

## `spread({ source, targets })`

### Motivation
Expand Down Expand Up @@ -35,7 +130,7 @@ spread({ source, targets: { field: target, ... } })

```ts
import { createStore, createEvent } from 'effector';
import { spread } from 'patronum/spread';
import { spread } from 'patronum';

const $first = createStore('');
const $last = createStore('');
Expand Down Expand Up @@ -96,8 +191,7 @@ save(null);

### Motivation

This overload creates event `source` that should be triggered and returns it.
It is useful to pass `source` immediately to another method as argument.
This overload recieves `targets` as an object. May be useful for additional clarity, but it's longer to write

### Formulae

Expand All @@ -116,77 +210,4 @@ source = spread({ targets: { field: target, ... } })

### Returns

- `source` `(Event<T>` | `Store<T>` | `Effect<T>)` — Source unit, data passed to it should be an object with fields from `targets`

### Example

#### Conditionally save value to stores

```ts
import { createStore, createEvent, sample } from 'effector';
import { spread } from 'patronum/spread';

const $first = createStore('');
const $last = createStore('');

const formReceived = createEvent();

sample({
source: formReceived,
filter: (form) => form.first.length > 0 && form.last.length > 0,
target: spread({
targets: {
first: $first,
last: $last,
},
}),
});

$first.watch((first) => console.log('First name', first));
$last.watch((last) => console.log('Last name', last));

formReceived({ first: '', last: '' });
// Nothing, because filter returned true

formReceived({ first: 'Hello', last: 'World' });
// => First name Hello
// => Last name World
```

#### Nested spreading

```ts
const trigger = createEvent();

const $targetA = createStore('');
const $targetB = createStore(0);
const $targetC = createStore(false);

spread({
source: trigger,
targets: {
first: $targetA,
second: spread({
targets: {
foo: $targetB,
bar: $targetC,
},
}),
},
});

$targetA.watch((payload) => console.log('targetA', payload));
$targetB.watch((payload) => console.log('targetB', payload));
$targetC.watch((payload) => console.log('targetC', payload));

trigger({
first: 'Hello',
second: {
foo: 200,
bar: true,
},
});
// => targetA Hello
// => targetB 200
// => targetC true
```
- `source` `(Event<T>)` — Source event, data passed to it should be an object with fields from `targets`
Loading

0 comments on commit de23027

Please sign in to comment.