0.3.2 • Published 5 months ago
nstate v0.3.2
nstate
A simple but powerful react state management library with low mental load, inspired by rko.
nstate = nano state / next state
Contents
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 for nested state updating
- Bind state field to form input with value/onChange/defaultValue
- Flexible, you can customize all internal methods by override.
Install
yarn add nstate # or npm i nstate
API
export function setDebug(boolean):void // enable debug log
export default class NState<T> {
protected state<T>
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<T>)
protected setState(patch: (s: T) => Partial<T>)
protected setState(patch: (draft: T) => void) // using immer under the hood
watch<U>(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<U>(handler: (s: U) => void) // remove watch listener
useWatch<U>(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<U>(getter: (s: T) => U): U // use state hook, based on `watch`, so you can return a new array/object for destructuring.
useBind<U>(getter: (s: T) => U): <K extends keyof U>(key: K, transformer?: (v: string) => U[K]) // bind state field to form input
useSubStore<U>(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<T, U>(state: T, actions: (store: LocalStore<T>) => U): [T, LocalStore<T> & U]
Usage
1. Counter example
import NState, { setDebug } from 'nstate'
import React from 'react'
setDebug(true) // enable debug log
interface Store {
count: number
}
export class CounterStore extends NState<Store> {
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<HTMLInputElement>(null)
return (
<div>
<div>
<h2>Counter</h2>
<p>count: {count}</p>
<button onClick={store.inc}>+</button>
<button onClick={store.dec}>-</button>
<button onClick={e=>store.set(0)}>reset</button>
</div>
</div>
)
}
export default Counter
2. Bind state field to form input with onChange/value` with type safety
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 (
<div>
count: {count}
<input type="text" {...bind('count', Number)} />
</div>
)
}
3. Combine multiple store to reuse actions/views
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<Store> {
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<HTMLInputElement>(null)
const inp2Ref = React.useRef<HTMLInputElement>(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 (
<div>
<div>
<h2>Combine</h2>
<Counter store={nestStore.counter} />
<p>aa: {aa}</p>
<p>bb: {bb}</p>
<input ref={inp1Ref} type="text" defaultValue={aa}/>
<button
onClick={e => {
nestStore.updateAA(inp1Ref.current?.value || '')
}}
>
set aa
</button>
<input ref={inp2Ref} type="text" defaultValue={bb}/>
<button
onClick={e => {
nestStore.updateBB(inp2Ref.current?.value || '')
}}
>
set bb
</button>
</div>
</div>
)
}
export default Combine
4. useLocalStore
function CounterWithLocalStore() {
const [count, store] = useLocalStore(0, store => ({
inc: () => store.setState(s => s + 1),
dec: () => store.setState(s => s - 1),
}))
return (
<div>
<div>
<h2>Counter with useLocalStore</h2>
<p>count: {count}</p>
<button onClick={store.inc}>+</button>
<button onClick={store.dec}>-</button>
<button onClick={e=>store.setState(0)}>reset</button>
</div>
</div>
)
}
5. useSubStore
interface Store {
counter1: {
count: number
}
counter2: {
count: number
}
}
export class Store extends NState<Store> {
}
export const store = new Store({ // initial state
counter1: {count: 1},
counter2: {count: 1},
})
function SubCounter({ store }) {
return (
<div>
<div>
<h2>Counter with useLocalStore</h2>
<p>count: {count}</p>
<button onClick={store.setState(s => s.count++)}>+</button>
<button onClick={store.setState(s => s.count--)}>-</button>
<button onClick={e=>store.setState(s => {
s.count = 0
})}>reset</button>
</div>
</div>
)
}
function Counter() {
const subStore = store.useSubStore(s => s.counter1, (s, u) => { s.counter1 = u })
return <SubCounter store={subStore} />
}
License
MIT
0.3.0
8 months ago
0.3.2
5 months ago
0.3.1
8 months ago
0.2.30
11 months ago
0.2.35
10 months ago
0.2.34
11 months ago
0.2.33
11 months ago
0.2.32
11 months ago
0.2.31
11 months ago
0.2.29
11 months ago
0.2.28
11 months ago
0.2.27
2 years ago
0.2.26
2 years ago
0.2.25
3 years ago
0.2.24
3 years ago
0.2.23
3 years ago
0.2.22
3 years ago
0.2.20
3 years ago
0.2.16
4 years ago
0.2.15
4 years ago
0.2.14
4 years ago
0.2.18
4 years ago
0.2.17
4 years ago
0.2.13
4 years ago
0.2.12
4 years ago
0.2.11
4 years ago
0.2.10
4 years ago
0.2.9
4 years ago
0.2.8
4 years ago
0.0.1
4 years ago
0.2.1
4 years ago
0.2.0
4 years ago
0.2.7
4 years ago
0.2.6
4 years ago
0.2.3
4 years ago
0.2.2
4 years ago
0.2.5
4 years ago
0.2.4
4 years ago
0.1.0
4 years ago