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

[@xstate/store] Event emitter #5064

Merged
merged 36 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5e688a3
Add emitting
davidkpiano Sep 2, 2024
28c5b02
Zod?
davidkpiano Sep 2, 2024
bdca556
Types or schema
davidkpiano Sep 2, 2024
0bf2c92
Export setup + changeset
davidkpiano Sep 4, 2024
ae2c03b
Update packages/xstate-store/src/setup.ts
davidkpiano Sep 5, 2024
612ff6c
Fix types
davidkpiano Sep 6, 2024
94fee03
Overload hell
davidkpiano Sep 6, 2024
4a74699
WIP
davidkpiano Sep 7, 2024
3faa641
Merge branch 'main' into davidkpiano/xstate-store-emit
davidkpiano Sep 10, 2024
4f4b4ec
Delete setup, update changeset
davidkpiano Sep 10, 2024
e795e0d
Add sub/unsub tests
davidkpiano Sep 10, 2024
a5c0ccf
Update packages/xstate-store/src/store.ts
davidkpiano Sep 10, 2024
87012f2
Update packages/xstate-store/test/fromStore.test.ts
davidkpiano Sep 10, 2024
3a83da9
Update packages/xstate-store/src/fromStore.ts
davidkpiano Sep 10, 2024
05e41f2
Update packages/xstate-store/src/fromStore.ts
davidkpiano Sep 10, 2024
480937f
Update packages/xstate-store/src/fromStore.ts
davidkpiano Sep 10, 2024
d696943
Address PR comments
davidkpiano Sep 12, 2024
c9400fc
Fix types with fromStore
davidkpiano Sep 12, 2024
75f3d32
Remove schemas (separate PR)
davidkpiano Sep 12, 2024
1c4ffc5
fix small things
Andarist Sep 12, 2024
24eaa22
remove redundant `NoInfer`
Andarist Sep 12, 2024
0d680b5
tweak types
Andarist Sep 12, 2024
a0c962b
fix knip
Andarist Sep 12, 2024
3ffa8dc
support `TTypes['events']`
Andarist Sep 12, 2024
8afda45
Update .changeset/silver-maps-grab.md
davidkpiano Sep 12, 2024
89989b9
Revert "support `TTypes['events']`"
davidkpiano Sep 13, 2024
9c92e0e
Simplify overload
davidkpiano Sep 13, 2024
aad549e
Update fromStore
davidkpiano Sep 13, 2024
bb91e03
Changeset
davidkpiano Sep 13, 2024
80c3a94
Fix emitted event ordering issue
davidkpiano Sep 13, 2024
7bbf78f
Emitted after observers
davidkpiano Sep 13, 2024
5cf6531
Merge remote-tracking branch 'origin/main' into davidkpiano/xstate-st…
Andarist Sep 15, 2024
2e934c8
move type tests to a separate file
Andarist Sep 15, 2024
c38a72c
add missing import, oops
Andarist Sep 15, 2024
db4f6f9
use a spy
Andarist Sep 15, 2024
996b511
fixed test title
Andarist Sep 15, 2024
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
56 changes: 56 additions & 0 deletions .changeset/silver-maps-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
'@xstate/store': minor
---

You can now emit events from a store:

```ts
import { createStore } from '@xstate/store';

const store = createStore({
context: {
count: 0
},
on: {
increment: (context, event, { emit }) => {
emit({ type: 'incremented' });
return { count: context.count + 1 };
}
}
});
davidkpiano marked this conversation as resolved.
Show resolved Hide resolved

store.on('incremented', () => {
console.log('incremented!');
});
```

You can make emitted events type-safe via `createstore({ schema: { … } })` (with Zod) or `createStore({ types: { … } })` (with coerced types):

```ts
import { createStore } from '@xstate/store';
import { z } from 'zod';

const store = createStore({
schema: {
emitted: {
incremented: z.object({ by: z.number() })
}
},
context: {
count: 0
},
on: {
increment: (context, event, { emit }) => {
emit({ type: 'incremented', by: 1 });
return { count: context.count + 1 };
}
}
});

store.on('incremented', (event) => {
console.log('Incremented by', event.by);
// => "Incremented by 1"
});

store.send({ type: 'increment' });
```
davidkpiano marked this conversation as resolved.
Show resolved Hide resolved
28 changes: 19 additions & 9 deletions packages/xstate-store/src/fromStore.ts
davidkpiano marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { createStore, createStoreTransition } from './store';
import { ActorLogic } from 'xstate';
import { createStoreTransition, TransitionsFromEventPayloadMap } from './store';
import {
EventPayloadMap,
StoreContext,
Snapshot,
StoreSnapshot,
Snapshot
EventObject,
ExtractEventsFromPayloadMap
} from './types';

type StoreLogic<
TContext extends StoreContext,
TEvent extends EventObject,
TInput
> = ActorLogic<StoreSnapshot<TContext>, TEvent, TInput, any, any>;

/**
* An actor logic creator which creates store [actor
* logic](https://stately.ai/docs/actors#actor-logic) for use with XState.
Expand All @@ -22,15 +31,16 @@ export function fromStore<
TInput
>(
initialContext: ((input: TInput) => TContext) | TContext,
transitions: Parameters<typeof createStore<TContext, TEventPayloadMap>>[1]
) {
const transition = createStoreTransition<TContext, TEventPayloadMap>(
transitions
);
transitions: TransitionsFromEventPayloadMap<
TEventPayloadMap,
NoInfer<TContext>,
EventObject
>
): StoreLogic<TContext, ExtractEventsFromPayloadMap<TEventPayloadMap>, TInput> {
const transition = createStoreTransition(transitions);
return {
transition,
start: () => {},
getInitialSnapshot: (_: any, input: TInput) => {
getInitialSnapshot: (_, input: TInput) => {
return {
status: 'active',
context:
Expand Down
6 changes: 3 additions & 3 deletions packages/xstate-store/src/react.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useCallback, useRef, useSyncExternalStore } from 'react';
import { Store, SnapshotFromStore } from './types';
import { Store, SnapshotFromStore, AnyStore } from './types';

function defaultCompare<T>(a: T | undefined, b: T) {
return a === b;
}

function useSelectorWithCompare<TStore extends Store<any, any>, T>(
function useSelectorWithCompare<TStore extends AnyStore, T>(
selector: (snapshot: SnapshotFromStore<TStore>) => T,
compare: (a: T | undefined, b: T) => boolean
): (snapshot: SnapshotFromStore<TStore>) => T {
Expand Down Expand Up @@ -40,7 +40,7 @@ function useSelectorWithCompare<TStore extends Store<any, any>, T>(
* previous value
* @returns The selected value
*/
export function useSelector<TStore extends Store<any, any>, T>(
export function useSelector<TStore extends AnyStore, T>(
store: TStore,
selector: (snapshot: SnapshotFromStore<TStore>) => T,
compare: (a: T | undefined, b: T) => boolean = defaultCompare
Expand Down
6 changes: 3 additions & 3 deletions packages/xstate-store/src/solid.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* @jsxImportSource solid-js */
import { createEffect, createSignal, onCleanup } from 'solid-js';
import type { Store, SnapshotFromStore } from './types';
import type { Store, SnapshotFromStore, AnyStore } from './types';

function defaultCompare<T>(a: T | undefined, b: T) {
return a === b;
}

function useSelectorWithCompare<TStore extends Store<any, any>, T>(
function useSelectorWithCompare<TStore extends AnyStore, T>(
selector: (snapshot: SnapshotFromStore<TStore>) => T,
compare: (a: T | undefined, b: T) => boolean
): (snapshot: SnapshotFromStore<TStore>) => T {
Expand Down Expand Up @@ -53,7 +53,7 @@ function useSelectorWithCompare<TStore extends Store<any, any>, T>(
* previously selected value
* @returns A read-only signal of the selected value
*/
export function useSelector<TStore extends Store<any, any>, T>(
export function useSelector<TStore extends AnyStore, T>(
store: TStore,
selector: (snapshot: SnapshotFromStore<TStore>) => T,
compare: (a: T | undefined, b: T) => boolean = defaultCompare
Expand Down
Loading