Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/samsze0/store
A zustand-like state management solution
https://github.com/samsze0/store
react zustand
Last synced: 3 months ago
JSON representation
A zustand-like state management solution
- Host: GitHub
- URL: https://github.com/samsze0/store
- Owner: samsze0
- License: mit
- Created: 2023-10-27T14:11:39.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2024-10-02T06:10:21.000Z (3 months ago)
- Last Synced: 2024-10-02T07:45:17.708Z (3 months ago)
- Topics: react, zustand
- Language: TypeScript
- Homepage:
- Size: 84 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# `store`
A zustand-like state management solution.
**Comparision with [Zustand](https://github.com/pmndrs/zustand)**:
- Support any state types. Zustand assumes state is an object.
- Addition of `DeriveStore` which creates a store that derives its state from any number of other stores.
- Middlewares are implemented using JS proxy.
- No cryptic TS typings in source code.## Usage
```shell
npm i @samsze/store
```**`ObjectStore`** (similar to Zustand-store)
```typescript
const counterStore = createObjectStore((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
}));// Argument to `create*Store` function can be a plain object too
const anotherStore = createObjectStore<{ x: number }>({ x: 3 });function Counter() {
const count = useStore(counterStore, (s) => s.count);
returncount: {count};
}
```**`SimpleStore`** (similar to Jotai atom)
```typescript
const store = createSimpleStore("Hello");store.setState("Hello");
```**`DeriveStore`** (similar to Jotai derive atom, but work with stores too)
```typescript
const depStore1 = createObjectStore({
value: [1],
});const depStore2 = createObjecStore({
value: [2],
});// Working on removing the need to specify types of stores as the second type arg
const deriveStore = derive(
[depStore1, depStore2],
([dep1, dep2], prevDeps, prevState) => {
return `${dep1} ${dep2}`;
}
);
```**Middleware**
```typescript
const store = subscribeWithSelector(
createObjectStore({
x: [1],
y: "testing",
})
);// Select a slice (a property of the state)
store.subscribe(
(state, prevState) => {
// ...
},
(state) => state.y,
false // partial?
);// Select a partial (subset of the state)
store.subscribe(
(state, prevState) => {
// ...
},
(state) => {
y: state.y;
},
true // partial?
);// Can be used as normal `subscribe` too
store.subscribe((state, prevState) => {
// ...
});
```## Middleware System - Design Decision
It seems like the majority of the complexity of Zustand comes from its middleware system. The challenge of creating such a middleware system is that the `Store` (i.e. `StoreApi` in Zustand terms) interface changes dynamically as middlewares are added to the store. For instance, when the `subscribeWithSelector` middleware is added to the store, the `subscribe` function takes a "selector" as an additional argument. In order to get type support for this behaviour, a lot of TS tricks are used.
This library's middleware system is facilited by JS Proxy. Each middleware must specify what `Store` interface it can accept as the input. For instance, the `subscribeWithSelector` middleware requires the input store to extends:
```typescript
export type ObjectStoreSubscribeListener = (state: T, prevState: T) => void;export type ObjectStoreUnsubscriber = () => void;
interface Store {
readonly setState: AnyFunction;
readonly getState: AnyFunction;
readonly subscribe: (
listener: ObjectStoreSubscribeListener
) => ObjectStoreUnsubscriber;
}
```This is because the `subscribeWithSelector` middleware will "hijack" the `subscribe` function of the input store and hence it expects it to be in a particular form.
Middleware must also specify the type of the store after its mutation. For instance, the output type of the `subscribeWithSelector` middleware will be:
```typescript
/**
* A type function that overwrites the properties of `T` with the properties of `U`.
*/
type Overwrite = Omit & U;type OutputStore = Overwrite<
InputStore,
{
/**
* A subscribe function with a selector function and a `partial` boolean field as additional arguments.
* @param listener A function that will be called when the subscribed state changes.
* @param selector A function that selects a slice or a partial of the state to subscribe to.
* @param partial A boolean field that indicates whether the selector function selects a partial of the state or a slice of the state.
* The field is used to determine how to equality checks should be performed, which in turn governs when the `listener` function will be triggered.
* - If `partial` is `false` or `undefined`, `Object.is` will be used to check for equality between states.
* - If `partial` is `true`, a 1-layer-deep `Object.is` will be used to check for equality between states.
* @returns void
*/
subscribe: CustomSubscribeFunction;
}
```## License
MIT