Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/zaaack/nstate

A simple but powerful react state management library with low mind burden
https://github.com/zaaack/nstate

class ddd ddd-architecture ddd-patterns immer mvc react react-state-management react-store reactive redux simple state-management

Last synced: 3 months ago
JSON representation

A simple but powerful react state management library with low mind burden

Awesome Lists containing this project

README

        

# nstate

[![publish](https://github.com/zaaack/nstate/actions/workflows/publish.yml/badge.svg)](https://github.com/zaaack/nstate/actions/workflows/publish.yml) [![npm](https://img.shields.io/npm/v/nstate.svg)](https://www.npmjs.com/package/nstate) [![npm](https://img.shields.io/npm/dm/nstate.svg)](https://www.npmjs.com/package/nstate) [![size](https://badgen.net/bundlephobia/minzip/nstate)](https://bundlephobia.com/package/nstate)

A simple but powerful react state management library with low mental load, inspired by [rko](https://github.com/steveruizok/rko).

> nstate = nano state / next state

## Contents

- [nstate](#nstate)
- [Contents](#contents)
- [Features](#features)
- [Install](#install)
- [API](#api)
- [Usage](#usage)
- [1. Counter example](#1-counter-example)
- [2. Bind state field to form input with onChange/value` with type safety](#2-bind-state-field-to-form-input-with-onchangevalue-with-type-safety)
- [3. Combine multiple store to reuse actions/views](#3-combine-multiple-store-to-reuse-actionsviews)
- [4. useLocalStore](#4-uselocalstore)
- [5. useSubStore](#5-usesubstore)
- [License](#license)

## Features

* Simple API with low mental load
* Powerful features based on concise API.
* Auto bind action methods
* Combine stores and reuse actions/views
* Watch store changes
* Shipped with [immer](https://immerjs.github.io/immer/) for nested state updating
* Bind state field to form input with value/onChange/defaultValue
* Flexible, you can customize all internal methods by override.

## Install

```sh
yarn add nstate # or npm i nstate
```

## API

```ts
export function setDebug(boolean):void // enable debug log

export default class NState {
protected state
protected events: Emitter<{
change: { patch: any, old: T }
}> // internal change events
constructor(initialState: T, nameOrOptions?: string | { name: string, debug: boolean})
protected onInit()
protected setState(patch: Partial)
protected setState(patch: (s: T) => Partial)
protected setState(patch: (draft: T) => void) // using immer under the hood
watch(getter: (s: T) => U, handler: (s: U) => void) // Watch deep state change, if getter return a new array(length <= 20) or object, it will be shallow equals
unwatch(handler: (s: U) => void) // remove watch listener
useWatch(getter: (s: T) => U, handler: (s: U) => void, deps?: any[]) // watch hooks wrapper for auto remove handler after unmount and auto update when deps changes
useState(getter: (s: T) => U): U // use state hook, based on `watch`, so you can return a new array/object for destructuring.
useBind(getter: (s: T) => U): (key: K, transformer?: (v: string) => U[K]) // bind state field to form input
useSubStore(getter: (s: T) => U, setter(s: T, u: U) => T) // create sub stores for get/set/watch, it will auto sync state to parent store
dispose() // clear all event listeners, for sub stores/local stores
}

export function useLocalStore(state: T, actions: (store: LocalStore) => U): [T, LocalStore & U]
```
## Usage

### 1. Counter example

```tsx
import NState, { setDebug } from 'nstate'
import React from 'react'

setDebug(true) // enable debug log

interface Store {
count: number
}
export class CounterStore extends NState {

inc() {
// setState by new state
this.setState({ count: this.state.count + 1 })
}

dec() {
// setState by updater function like React
this.setState(s => ({ count: s.count - 1 }))
}

set(n: number) {
// setState by immer draft (using immer under the hood)
this.setState(draft => {
draft.count = n
})
}
}

export const counterStore = new CounterStore({ // optional initial state
count: 0,
})

function Counter({ store = counterStore }: { store?: CounterStore }) {
const count = store.useState(s => s.count)
const inpRef = React.useRef(null)
return (



Counter


count: {count}


+
-
store.set(0)}>reset


)
}

export default Counter
```

### 2. Bind state field to form input with onChange/value` with type safety

```tsx
function Counter() {
const count = counterStore.useState(s => s.count)
const bind = counterStore.useBind(s => s) // you can also bind nested object with (s => s.xx.aa)
return (


count: {count}


)
}

```

### 3. Combine multiple store to reuse actions/views

```tsx
import NState, { setDebug } from 'nstate'
import React from 'react'
import Counter, {CounterStore} from './Counter';

setDebug(true) // enable debug log
interface Store {
nest: {
aa: string,
bb: string,
}
}
export class CombineStore extends NState {

counter = new CounterStore({ count: 1 })

onInit() {
// link to counter store by simple watch API
this.counter.watch(s=> s.count, count => {
this.updateAA('count changed:'+count)
})
}

updateAA(aa: string) {
this.setState(draft => {
draft.nest.aa = aa
})
}
updateBB(bb: string) {
this.setState(draft => {
draft.nest.bb = bb
})
}
}

export const nestStore = new CombineStore({ // initial state
nest: {aa: 'aa', bb: 'bb'},
})

function Combine() {
// use state by destructuring, support array/object
const [aa, bb] = nestStore.useState(s => [s.nest.aa, s.nest.bb])
// or:
// const {aa, bb} = nestStore.useState(s => ({aa: s.nest.aa, bb: s.nest.bb}))
const inp1Ref = React.useRef(null)
const inp2Ref = React.useRef(null)
// watch hooks wrapper for auto remove handler after unmount
nestStore.useWatch(s => [s.nest.aa, s.nest.bb], [aa, bb] => {
// do something when state changes
})
return (



Combine



aa: {aa}


bb: {bb}



{
nestStore.updateAA(inp1Ref.current?.value || '')
}}
>
set aa


{
nestStore.updateBB(inp2Ref.current?.value || '')
}}
>
set bb



)
}

export default Combine
```

### 4. useLocalStore

```tsx
function CounterWithLocalStore() {
const [count, store] = useLocalStore(0, store => ({
inc: () => store.setState(s => s + 1),
dec: () => store.setState(s => s - 1),
}))
return (



Counter with useLocalStore


count: {count}


+
-
store.setState(0)}>reset


)
}
```

### 5. useSubStore

```tsx
interface Store {
counter1: {
count: number
}
counter2: {
count: number
}
}
export class Store extends NState {

}
export const store = new Store({ // initial state
counter1: {count: 1},
counter2: {count: 1},
})

function SubCounter({ store }) {
return (



Counter with useLocalStore


count: {count}


s.count++)}>+
s.count--)}>-
store.setState(s => {
s.count = 0
})}>reset


)
}
function Counter() {
const subStore = store.useSubStore(s => s.counter1, (s, u) => { s.counter1 = u })
return
}
```

## License

MIT