@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(){
......
});