
@airma/react-state
Simple reducer-like
state-management with method action dispatch mode for react components.
Documents
Code first
Create reducer-like
function:
export function counting(state:number){
return {
count: state,
increase:()=>state + 1,
decrease:()=>state - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, state);
}
};
}
Use reducer-like
function:
import {counting} from './model';
import {useModel} from '@airma/react-state';
......
const {count, increase, decrease, add} = useModel(counting, 0);
......
The reducer-like
function has a simple name model
. Use API model
can make it more simple.
Local state management
import {model} from '@airma/react-state';
const counting = model(function counting(state:number){
return {
count: state,
increase:()=>state + 1,
decrease:()=>state - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, state);
}
};
});
......
const {count, increase, decrease, add} = counting.useModel(0);
......
Though, the basic function about model
is enhancing React.useReducer
to manage a local state, it also can be used to manage a scope state from dynamic store or static store.
Dynamic store state management
API createKey
can create a model template for creating a dynamic store. The template is also a key to synchronize state changes from store.
import {memo} from 'react';
import {model, provide} from '@airma/react-state';
const countingKey = model(function counting(state:number){
return {
count: state,
increase:()=>state + 1,
decrease:()=>state - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, state);
}
};
}).createKey(0);
......
const Increase = memo(()=>{
const increase = countingKey.useSelector(i => i.increase);
return <button onClick={increase}>+</button>;
});
const Count = memo(()=>{
const {count} = countingKey.useModel();
return <span>{count}</span>;
});
const Decrease = memo(()=>{
const decrease = countingKey.useSelector(i => i.decrease);
return <button onClick={decrease}>-</button>;
});
const Component = provide(countingKey).to(function Comp() {
return (
<div>
<Increase/>
<Count/>
<Decrease/>
</div>
);
});
......
A dynamic store should be created in a component, and synchronized in the children components by using React.Context
.
A static store should be created in a global scope, and used in any component without provider.
Using model(xxx).createStore()
can build a static store.
Static store state management
import {model} from '@airma/react-state';
const countingStore = model(function counting(state:number){
return {
count: state,
increase:()=>state + 1,
decrease:()=>state - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, state);
}
};
}).createStore(0);
......
const Increase = memo(()=>{
const increase = countingStore.useSelector(i => i.increase);
return <button onClick={increase}>+</button>;
});
const Count = memo(()=>{
const {count} = countingStore.useModel();
return <span>{count}</span>;
});
const Decrease = memo(()=>{
const decrease = countingStore.useSelector(i => i.decrease);
return <button onClick={decrease}>-</button>;
});
const Component = function Comp() {
return (
<div>
<Increase/>
<Count/>
<Decrease/>
</div>
);
};
The useSelector
API is helpful for reducing render frequency, only when the selected result is changed, it make its owner component rerender.
A high performance usage about useSignal
In @airma/react-state@18.4.0
, a more simple and higher performance API useSignal
is provided.
import {model} from '@airma/react-state';
const counting = model(function countingModel(state:number){
return {
count: state,
increase:()=>state + 1,
decrease:()=>state - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, state);
}
};
}).createStore();
......
const Increase = memo(()=>{
const signal = counting.useSignal();
return <button onClick={signal().increase}>+</button>;
});
const Count = memo(()=>{
const signal = counting.useSignal();
return <span>{signal().count}</span>;
});
const Decrease = memo(()=>{
const signal = counting.useSignal();
return <button onClick={signal().decrease}>-</button>;
});
const Component = function Comp({defaultCount}:{defaultCount:number}) {
counting.useSignal(defaultCount);
return (
<div>
<Increase/>
<Count/>
<Decrease/>
</div>
);
};
The useSignal
API is even better than API useSelector
, it computes out when to rerender component by the fields getting from instance automatically. And by using the signal
function, it always provides a newest instance in usage point, so it can avoid stale data and zombie-children problems more effectively.
Why support context store?
The context store is a dynamic store, it has some better features than a static store.
- The store data can be destroyed with its
owner
component unmount.
- Components with same store factory creates different stores.
How to subscribe a grand parent provider store?
The store provider system in @airma/react-state
is designed with a tree structure. The nearest provider
finds store one-by-one from itself to its root parent provider
, and links the nearest matched provider
store to the subscriber useModel/useSelector
.
Does the state change of store leads a whole provider component rerender?
No, only the hooks subscribing this store
may rerender their owners. Every store change is notified to its subscriber like useModel
and useSelector
, and then the subscriber rerenders its owner by useState
.
Why not support async action methods
Async action often makes stale data problem and zombie-children problem. So, a special tool to resolve this problem is necessary, you can try @airma/react-effect with it.
There are more examples, concepts and APIs in the documents of @airma/react-state
.
Browser Support
chrome: '>=91',
edge: '>=91',
firefox: '=>90',
safari: '>=15'