-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Update JS docs * Add small example * Move to store-counter-react * Update example README.md * Add readme * Changeset * ctx -> context
- Loading branch information
1 parent
4a22edb
commit 3a57f4c
Showing
2 changed files
with
180 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@xstate/store': patch | ||
--- | ||
|
||
Update README.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,176 @@ | ||
# `@xstate/store` | ||
|
||
XState Store is a library for **simple event-based state management**. If you want a state management library that allows you to update a store's state via events, `@xstate/store` is a great option. If you need more complex application logic needs, like state machines/statecharts, effects, communicating actors, and more, consider [using XState instead](https://github.com/statelyai/xstate). | ||
|
||
- **Extremely simple**: transitions update state via events, just like Redux, Zustand, Pinia, etc. | ||
- **Extremely small**: less than 1kb minified/gzipped | ||
- **XState compatible**: use it with (or without) XState, or convert to XState machines when you need to handle more complex logic & effects. | ||
- **Extra type-safe**: great typing out of the box, with strong inference and no awkwardness. | ||
|
||
> [!NOTE] | ||
> This readme is written for [TypeScript](#typescript) users. If you are a JavaScript user, just remove the types. | ||
## Installation | ||
|
||
```bash | ||
# yarn add @xstate/store | ||
# pnpm add @xstate/store | ||
npm install @xstate/store | ||
``` | ||
|
||
## Quick start | ||
|
||
```ts | ||
import { createStore } from '@xstate/store'; | ||
|
||
// 1. Create a store | ||
export const donutStore = createStore( | ||
{ | ||
donuts: 0, | ||
favoriteFlavor: 'chocolate' | ||
}, | ||
{ | ||
addDonut: { | ||
donuts: (context) => context.donuts + 1 | ||
}, | ||
changeFlavor: { | ||
favoriteFlavor: (context, event: { flavor: string }) => event.flavor | ||
}, | ||
eatAllDonuts: { | ||
donuts: 0 | ||
} | ||
} | ||
); | ||
|
||
console.log(store.getSnapshot()); | ||
// { | ||
// status: 'active', | ||
// context: { | ||
// donuts: 0, | ||
// favoriteFlavor: 'chocolate' | ||
// } | ||
// } | ||
|
||
// 2. Subscribe to the store | ||
store.subscribe((snapshot) => { | ||
console.log(snapshot.context); | ||
}); | ||
|
||
// 3. Send events | ||
store.send({ type: 'addDonut' }); | ||
// logs { donuts: 1, favoriteFlavor: 'chocolate' } | ||
|
||
store.send({ | ||
type: 'changeFlavor', | ||
flavor: 'strawberry' // Strongly-typed! | ||
}); | ||
// logs { donuts; 1, favoriteFlavor: 'strawberry' } | ||
``` | ||
|
||
## Usage with React | ||
|
||
Import `useSelector` from `@xstate/store/react`. Select the data you want via `useSelector(…)` and send events using `store.send(eventObject)`: | ||
|
||
```tsx | ||
import { donutStore } from './donutStore.ts'; | ||
import { useSelector } from '@xstate/store/react'; | ||
|
||
function DonutCounter() { | ||
const donutCount = useSelector(donutStore, (state) => state.context.donuts); | ||
|
||
return ( | ||
<div> | ||
<button onClick={() => donutStore.send({ type: 'addDonut' })}> | ||
Add donut ({donutCount}) | ||
</button> | ||
</div> | ||
); | ||
} | ||
``` | ||
|
||
## Usage with Immer | ||
|
||
XState Store makes it really to integrate with immutable update libraries like [Immer](https://github.com/immerjs/immer) or [Mutative](https://github.com/unadlib/mutative). Pass the `produce` function into `createStoreWithProducer(producer, …)`, and update `context` in transition functions using the convenient pseudo-mutative API: | ||
|
||
```ts | ||
import { createStoreWithProducer } from '@xstate/store'; | ||
import { produce } from 'immer'; // or { create } from 'mutative' | ||
|
||
const donutStore = createStoreWithProducer( | ||
produce, | ||
{ | ||
donuts: 0, | ||
favoriteFlavor: 'chocolate' | ||
}, | ||
{ | ||
addDonut: (context) => { | ||
context.donuts++; // "Mutation" (thanks to the producer) | ||
}, | ||
changeFlavor: (context, event: { flavor: string }) => { | ||
context.favoriteFlavor = event.flavor; | ||
}, | ||
eatAllDonuts: (context) => { | ||
context.donuts = 0; | ||
} | ||
} | ||
); | ||
|
||
// Everything else is the same! | ||
``` | ||
|
||
## TypeScript | ||
|
||
XState Store is written in TypeScript and provides full type safety, _without_ you having to specify generic type parameters. The `context` type is inferred from the initial context object, and the event types are inferred from the event object payloads you provide in the transition functions. | ||
|
||
```ts | ||
import { createStore } from '@xstate/store'; | ||
|
||
const donutStore = createStore( | ||
// Inferred as: | ||
// { | ||
// donuts: number; | ||
// favoriteFlavor: string; | ||
// } | ||
{ | ||
donuts: 0, | ||
favoriteFlavor: 'chocolate' | ||
}, | ||
{ | ||
// Event inferred as: | ||
// { | ||
// type: 'changeFlavor'; | ||
// flavor: string; | ||
// } | ||
changeFlavor: (context, event: { flavor: string }) => { | ||
context.favoriteFlavor = event.flavor; | ||
} | ||
} | ||
); | ||
|
||
donutStore.getSnapshot().context.favoriteFlavor; // string | ||
|
||
donutStore.send({ | ||
type: 'changeFlavor', // Strongly-typed from transition key | ||
flavor: 'strawberry' // Strongly-typed from { flavor: string } | ||
}); | ||
``` | ||
|
||
If you want to make the `context` type more specific, you can strongly type the `context` outside of `createStore(…)` and pass it in: | ||
|
||
```ts | ||
import { createStore } from '@xstate/store'; | ||
|
||
interface DonutContext { | ||
donuts: number; | ||
favoriteFlavor: 'chocolate' | 'strawberry' | 'blueberry'; | ||
} | ||
|
||
const donutContext: DonutContext = { | ||
donuts: 0, | ||
favoriteFlavor: 'chocolate' | ||
}; | ||
|
||
const donutStore = createStore(donutContext, { | ||
// ... (transitions go here) | ||
}); | ||
``` |