diff --git a/src/spread/index.ts b/src/spread/index.ts index db7f1130..6dc5aa57 100644 --- a/src/spread/index.ts +++ b/src/spread/index.ts @@ -1,11 +1,4 @@ -import { - createEvent, - EventCallable, - is, - sample, - Unit, - UnitTargetable, -} from 'effector'; +import { createEvent, EventCallable, sample, Unit, UnitTargetable } from 'effector'; const hasPropBase = {}.hasOwnProperty; const hasOwnProp = (object: O, key: string) => @@ -13,9 +6,29 @@ const hasOwnProp = (object: O, key: string) type NoInfer = [T][T extends any ? 0 : never]; +/** + * @example + * spread({ + * source: dataObject, + * targets: { first: targetA, second: [target1, target2] }, + * }) + * + * sample({ + * source: dataObject, + * target: spread({ targets: { first: targetA, second: [target1, target2] } }) + * }) + * + * sample({ + * source: dataObject, + * target: spread({ first: targetA, second: [target1, target2] }) + * }) + */ + export function spread(config: { targets: { - [Key in keyof Payload]?: UnitTargetable; + [Key in keyof Payload]?: + | UnitTargetable + | Array>; }; }): EventCallable>; @@ -27,31 +40,28 @@ export function spread< targets: { [Key in keyof Payload]?: | EventCallable> - | UnitTargetable>; + | Array>> + | UnitTargetable> + | Array>>; }; }): Source; export function spread(targets: { - [Key in keyof Payload]?: UnitTargetable; + [Key in keyof Payload]?: + | UnitTargetable + | Array>; }): EventCallable>; -/** - * @example - * spread({ source: dataObject, targets: { first: targetA, second: targetB } }) - * sample({ - * target: spread({targets: { first: targetA, second: targetB } }) - * }) - */ export function spread

( args: | { targets: { - [Key in keyof P]?: Unit; + [Key in keyof P]?: Unit | Array>; }; source?: Unit

; } | { - [Key in keyof P]?: Unit; + [Key in keyof P]?: Unit | Array>; }, ): EventCallable

{ const argsShape = isTargets(args) ? { targets: args } : args; @@ -83,18 +93,15 @@ function isTargets

( args: | { targets: { - [Key in keyof P]?: Unit; + [Key in keyof P]?: Unit | Array>; }; source?: Unit

; } | { - [Key in keyof P]?: Unit; + [Key in keyof P]?: Unit | Array>; }, ): args is { - [Key in keyof P]?: Unit; + [Key in keyof P]?: Unit | Array>; } { - return Object.keys(args).some( - (key) => - !['targets', 'source'].includes(key) && is.unit(args[key as keyof typeof args]), - ); + return !Object.keys(args).some((key) => ['targets', 'source'].includes(key)); } diff --git a/src/spread/readme.md b/src/spread/readme.md index 27bdf9b7..bb4bb9b4 100644 --- a/src/spread/readme.md +++ b/src/spread/readme.md @@ -211,3 +211,61 @@ source = spread({ targets: { field: target, ... } }) ### Returns - `source` `(Event)` — Source event, data passed to it should be an object with fields from `targets` + +## `source = spread({ targets: { field: Unit[] } })` + +### Motivation + +Multiple units can be passed for each target field + +### Formulae + +```ts +source = spread({ field: [target1, target2], ... }) + +source = spread({ targets: { field: [target1, target2], ... } }) + +spread({ source, targets: { field: [target1, target2], ... } }) +``` + +- When `source` is triggered with **object**, extract `field` from data, and trigger all targets of `target` +- `targets` can have multiple properties with multiple units +- If the `source` was triggered with non-object, nothing would be happening +- If `source` is triggered with object but without property `field`, no unit of the target for this `field` will be triggered + +### Example + +#### Trigger multiple units for each field of payload + +```ts +const roomEntered = createEvent<{ + roomId: string; + userId: string; + message: string; +}>(); +const userIdChanged = createEvent(); + +const $roomMessage = createStore(''); +const $currentRoomId = createStore(null); + +const getRoomFx = createEffect((roomId: string) => roomId); +const setUserIdFx = createEffect((userId: string) => userId); + +sample({ + clock: roomEntered, + target: spread({ + roomId: [getRoomFx, $currentRoomId], + userId: [setUserIdFx, userIdChanged], + message: $roomMessage, + }), +}); + +roomEntered({ + roomId: 'roomId', + userId: 'userId', + message: 'message', +}); +// => getRoomFx('roomId'), update $currentRoomId with 'roomId' +// => setUserIdFx('userId'), userIdChanged('userId') +// => update $roomMessage with 'message' +``` diff --git a/src/spread/spread.test.ts b/src/spread/spread.test.ts index b3fef538..3d6a17eb 100644 --- a/src/spread/spread.test.ts +++ b/src/spread/spread.test.ts @@ -1,4 +1,4 @@ -import { combine, createEvent, createStore, sample } from 'effector'; +import { combine, createEffect, createEvent, createStore, sample } from 'effector'; import { spread } from './index'; describe('spread(source, targets)', () => { @@ -105,6 +105,44 @@ describe('spread(source, targets)', () => { expect(fnA).toBeCalledWith('Hello'); expect(fnB).toBeCalledWith(200); }); + + test('unit to array of units', () => { + const source = createEvent<{ first: string; second: number; third: string }>(); + const targetA = createEvent(); + const targetA2 = createEffect(); + const targetB = createEvent(); + const targetB2 = createEvent(); + const targetC = createStore(''); + + const fnA = jest.fn(); + const fnB = jest.fn(); + const fnA1 = jest.fn(); + const fnB1 = jest.fn(); + const fnC = jest.fn(); + + targetA.watch(fnA); + targetB.watch(fnB); + targetA2.watch(fnA1); + targetB2.watch(fnB1); + targetC.watch(fnC); + + spread({ + source, + targets: { + first: [targetA, targetA2], + second: [targetB, targetB2], + third: targetC, + }, + }); + + source({ first: 'Hello', second: 200, third: 'third' }); + + expect(fnA).toBeCalledWith('Hello'); + expect(fnB).toBeCalledWith(200); + expect(fnA1).toBeCalledWith('Hello'); + expect(fnB1).toBeCalledWith(200); + expect(fnC).toBeCalledWith('third'); + }); }); describe('spread(targets)', () => { @@ -133,6 +171,7 @@ describe('spread(targets)', () => { expect(fnA).toBeCalledWith('Hello'); expect(fnB).toBeCalledWith(200); }); + test('event to events (shorthand)', () => { const source = createEvent<{ first: string; second: number }>(); const targetA = createEvent(); @@ -266,6 +305,84 @@ describe('spread(targets)', () => { expect(fnA).toBeCalledWith('Hello'); expect(fnB).toBeCalledWith(200); }); + + test('unit to array of units', () => { + const source = createEvent<{ first: string; second: number; third: string }>(); + const targetA = createEvent(); + const targetA2 = createEffect(); + const targetB = createEvent(); + const targetB2 = createEvent(); + const targetC = createStore(''); + + const fnA = jest.fn(); + const fnB = jest.fn(); + const fnA1 = jest.fn(); + const fnB1 = jest.fn(); + const fnC = jest.fn(); + + targetA.watch(fnA); + targetB.watch(fnB); + targetA2.watch(fnA1); + targetB2.watch(fnB1); + targetC.watch(fnC); + + sample({ + source, + target: spread({ + targets: { + first: [targetA, targetA2], + second: [targetB, targetB2], + third: targetC, + }, + }), + }); + + source({ first: 'Hello', second: 200, third: 'third' }); + + expect(fnA).toBeCalledWith('Hello'); + expect(fnB).toBeCalledWith(200); + expect(fnA1).toBeCalledWith('Hello'); + expect(fnB1).toBeCalledWith(200); + expect(fnC).toBeCalledWith('third'); + }); + + test('unit to array of units (shorthand)', () => { + const source = createEvent<{ first: string; second: number; third: string }>(); + const targetA = createEvent(); + const targetA2 = createEffect(); + const targetB = createEvent(); + const targetB2 = createEvent(); + const targetC = createStore(''); + + const fnA = jest.fn(); + const fnB = jest.fn(); + const fnA1 = jest.fn(); + const fnB1 = jest.fn(); + const fnC = jest.fn(); + + targetA.watch(fnA); + targetB.watch(fnB); + targetA2.watch(fnA1); + targetB2.watch(fnB1); + targetC.watch(fnC); + + sample({ + source, + target: spread({ + first: [targetA, targetA2], + second: [targetB, targetB2], + third: targetC, + }), + }); + + source({ first: 'Hello', second: 200, third: 'third' }); + + expect(fnA).toBeCalledWith('Hello'); + expect(fnB).toBeCalledWith(200); + expect(fnA1).toBeCalledWith('Hello'); + expect(fnB1).toBeCalledWith(200); + expect(fnC).toBeCalledWith('third'); + }); }); describe('edge', () => {