@airma provides simple and useful packages for react developing env:
Simple reducer-like state-management with method action dispatch mode for react components.
Create reducer-like function:
export function counting(state:number){
return {
// reproduced state for render
count: state,
// action method
increase:()=>state + 1,
// action method
decrease:()=>state - 1,
// action method, define parameters freely.
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';
......
// give it an initialState can make it fly.
const {count, increase, decrease, add} = useModel(counting, 0); // initialState `0`
// call method `increase\decrease\add` can change `count` and make component rerender
......The reducer-like function has a simple name model. Use API model can make it more simple.
import {model} from '@airma/react-state';
// api model returns a wrap function for your model function.
// it keeps a same type of parameters and return data with the wrapped function.
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);
}
};
});
......
// you can get useModel from the model wrapped function.
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 supports store usages.
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,
};
}).createKey(0);
......
const Increase = memo(()=>{
// use key.useSelector to sync state changes from store,
// when the selected result is changed it rerender component.
const increase = countingKey.useSelector(i => i.increase);
return <button onClick={increase}>+</button>;
});
const Count = memo(()=>{
// use key.useModel to sync state changes from store.
const {count} = countingKey.useModel();
return <span>{count}</span>;
});
const Decrease = memo(()=>{
const decrease = countingKey.useSelector(i => i.decrease);
return <button onClick={decrease}>-</button>;
});
// provide key to component for creating a dynamic store in Component
const Component = provide(countingKey).to(function Comp() {
return (
<div>
<Increase/>
<Count/>
<Decrease/>
</div>
);
});
......The keys are templates for creating store in component. When the component generates an element, it creates a store in this element, and provide the store to its children. This store is a dynamic store.
The dynamic store is created when component creates elements, that makes the elements from one component takes different stores with same temlates.
The static store is simple, it needs no Context tech support. Using model(xxx).createStore() can build a static store.
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>;
});
// A static store needs no provider.
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.
There are more examples, concepts and APIs in the documents of @airma/react-state.
Simple asynchronous state-management for react. It is considered as a composite hook by React.useEffect and React.useState.
Create a callback which always returns a promise.
// parameters groupId
export function fetchUsers(groupId: number): Promise<User[]> {
return userRequest.fetchUsersByGroupId(groupId);
}Use asynchronous callback.
import {fetchUsers} from './session';
import {useQuery} from '@airma/react-effect';
......
// useQuery calls `fetchUsers` just like a `useEffect` works.
// When the owner component is mounting, or each variable([props.groupId]) is changing, the `fetchUsers` is called.
const [
sessionState,
recall,
recallWithVariables
] = useQuery(fetchUsers, [props.groupId]);
const {
// (Users[] | undefined), the promise resolving data.
// Before useQuery works out, it is undefined.
data: users,
// boolean, if useQuery is fetching data.
isFetching,
// boolean, if current query has a rejection.
isError,
// unknown, the promise rejection
error,
// (undefined | Parameters<typeof fetchUsers>),
// The parameters of current query result.
// Before useQuery works out, it is undefined.
variables,
// boolean, if useQuery has fetched data successfully.
loaded
} = sessionState;
......
// call `recall` function can trigger useQuery works manually.
recall();
......
// call `recallWithVariables` function can trigger useQuery works manually with temporary parameters.
recallWithVariables(props.groupId);
......Every time useQuery fetches a latest data as sessionState.data by calling asynchronous callback, it is very useful.
The asynchronous callback for useQuery or useMutation is named session in @airma/react-effect. It makes a simple usage API like model for @airma/react-state.
import {session} from '@airma/react-effect';
const fetchUsersSession = session((groupId: number): Promise<User[]> => {
return userRequest.fetchUsersByGroupId(groupId);
}, 'query'); // use sessionType `query` to mark out, it is a session for `useQuery` not `useMutation`.
......
const [
sessionState,
recall,
recallWithVariables
] = fetchUsersSession.useQuery([props.groupId]);
......API useQuery or useMutation can be used as a context or global asynchronous state-management hook too.
import {session, provide} from '@airma/react-effect';
// create a key for syncing state changes.
const fetchUsersSessionKey = session((groupId: number): Promise<User[]> => {
return userRequest.fetchUsersByGroupId(groupId);
}, 'query').createKey();
......
const ChildQueryComponent = ()=>{
......
// when `fetchUsersSessionKey.useQuery` works,
// the `sessionState change` happens in a store,
// the other session usages like `useSession` with same key receives same change.
const [
sessionState,
recall,
recallWithVariables
] = fetchUsersSessionKey.useQuery([props.groupId]);
......
};
const ChildReceptionComponent = ()=>{
......
// the key.useSession can accept the sessionState changes caused by the same store `useQuery` or `useMutation`.
const [
sessionState,
recall
] = fetchUsersSessionKey.useSession();
......
};
// provide keys to component for creating a dynamic store.
const Component = provide(fetchUsersSessionKey).to(function Comp(){
return (
<>
<ChildQueryComponent/>
<ChildReceptionComponent/>
</>
);
});Use session(xxx,'query'|'mutation').createStore() can create a static store, which can be used without provider.
import {session} from '@airma/react-effect';
// create static store
const fetchUsersSession = session((groupId: number): Promise<User[]> => {
return userRequest.fetchUsersByGroupId(groupId);
}, 'query').createStore();
......
const ChildQueryComponent = ()=>{
......
const [
sessionState,
recall,
recallWithVariables
] = fetchUsersSession.useQuery([props.groupId]);
......
};
const ChildReceptionComponent = ()=>{
......
const [
sessionState,
recall
] = fetchUsersSession.useSession();
......
};
// The static store API can be used directly without provider.
const Component = function Comp(){
return (
<>
<ChildQueryComponent/>
<ChildReceptionComponent/>
</>
);
};The API useMutation or (session\sessionStore).useMutation is similar with useQuery.
The different is useMutation should be triggered to work manually.
import {session} from '@airma/react-effect';
const saveUserSession = session((user: User): Promise<void> => {
return userRequest.saveUser(user);
}, 'mutation'); // use sessionType `mutation` to mark out, it is a session for `useMutation` not `useQuery`.
......
const [
sessionState,
recall,
recallWithVariables
] = saveUserSession.useMutation([state.user]);
......
// trigger it manually
recall();
......
recallWithVariables();The useMutation API works with a block mode. If it is working, it refuses other triggered missions.
Set triggerOn config property to useQuery or useMutation can change the default trigger ways of these hooks.
Make useMutation works when variables changes.
import {session} from '@airma/react-effect';
const saveUserSession = session((user: User): Promise<void> => {
return userRequest.saveUser(user);
}, 'mutation');
......
const [
sessionState,
recall,
recallWithVariables
] = saveUserSession.useMutation({
variables: [state.user],
// This setting makes useMutation works when `state.user` changes.
triggerOn: ['update', 'manual']
});
......
recall();
......
recallWithVariables();Be careful if the config of useMutation is using update or mount trigger way, it works without a block protection mode.
Make useQuery works only when it is triggered manually.
import {session} from '@airma/react-effect';
const fetchUsersSession = session((groupId: number): Promise<User[]> => {
return userRequest.fetchUsersByGroupId(groupId);
}, 'query');
......
const [
sessionState,
recall,
recallWithVariables
] = fetchUsersSession.useQuery({
variables: [props.groupId],
triggerOn: ['manual']
});
......There are full strategies to decorate the actions about useQuery and useMutation, like debounce, throttle, once, memo, and so on.
Set these strategies to useQuery or useMutation can help session works better.
import {session} from '@airma/react-effect';
const fetchUsersSession = session((groupId: number): Promise<User[]> => {
return userRequest.fetchUsersByGroupId(groupId);
}, 'query');
......
const [
sessionState,
recall,
recallWithVariables
] = fetchUsersSession.useQuery({
variables: [props.groupId],
// Strategy.debounce makes useQuery works debounce with 300 ms duration time.
// Strategy.memo makes useQuery use the old sessionState.data, if the work out data equals old data by calling `JSON.stringify`.
strategy: [Strategy.debounce(300), Strategy.memo()]
});
......@airma/react-effect dependent @airma/react-state, and the state sharing way is just like @airma/react-state.
There are more examples, concepts and APIs in the documents of @airma/react-effect.
A lot of APIs about @airma/react-state and @airma/react-effect are too similar. So, @airma/react-hooks is a better choosen for using both of them. It combine these two packages APIs together.
import {model, session} from '@airma/react-hooks';
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 fetchUsersSession = session((groupId: number): Promise<User[]> => {
return userRequest.fetchUsersByGroupId(groupId);
}, 'query').createStore();
......
// combine different stores together, and provide to a root component
const Component = countingStore.with(fetchUsersSession).provideTo(function Comp(){
......
});