@airma/react-effect
@airma/react-effect
is an asynchronous state-management tool for react.
Document
Code first
useQuery
API useQuery
can query data, and set it as a state.
import React from 'react';
import {useQuery} from '@airma/react-effect';
import {User} from './type';
type UserQuery = {
name: string;
username: string;
}
const fetchUsers = (query: UserQuery):Promise<User[]> =>
Promise.resolve([]);
const App = ()=>{
const [query, setQuery] = useState({name:'', username:''});
const [state, trigger, executeWithParams] = useQuery(
fetchUsers,
[query]
);
const {
data,
isFetching,
error,
isError,
loaded
} = state;
......
}
When useQuery
is mounted, or the dependency parameters change, it calls the promise callback.
UseMutation
API useMutation
is similar with useQuery
. The difference is that it should be triggered manually to work.
import React from 'react';
import {useMutation} from '@airma/react-effect';
import {User} from './type';
const saveUser = (user: User): Promise<User> =>
Promise.resolve(user);
const App = ()=>{
const [user, setUser] = useState<User>({...});
const [
state,
trigger,
executeWithParams
] = useMutation(
saveUser,
[ user ]
);
const {
data,
isFetching,
error,
isError
} = result;
const handleClick = ()=>{
trigger();
}
......
}
The state of useMutation has same fields with useQuery state.
Session
Both of useQuery and useMutation need a promise callback for working, the mission of promise callback is called session.
Use a simplified API session to make coding fly.
import React from 'react';
import {session} from '@airma/react-effect';
import {User} from './type';
type UserQuery = {
name: string;
username: string;
}
const userQuerySession = session(
(query: UserQuery):Promise<User[]> =>
Promise.resolve([]),
'query'
);
const App = ()=>{
const [query, setQuery] = useState({name:'', username:''});
const [
state,
trigger,
executeWithParams
] = userQuerySession.useQuery(
[query]
);
const {
data,
isFetching,
error,
isError,
loaded
} = state;
......
}
The state of useQuery/useMutation is a local state. There are two different store state-managements: use dynamic React.Context store or use static global store.
React.Context dynamic store state-management
import React from 'react';
import {session} from '@airma/react-effect';
import {User} from './type';
type UserQuery = {
name: string;
username: string;
}
const userQueryStore = session(
(query: UserQuery):Promise<User[]> =>
Promise.resolve([]),
'query'
).createStore();
const SearchButton = ()=>{
const [
{isFetching},
triggerQuery
] = userQueryStore.useSession();
return (
<button
disabled={isFetching}
onClick={triggerQuery}
>
query
</button>
);
}
const App = userQueryStore.provideTo(()=>{
const [query, setQuery] = useState({name:'', username:''});
const [
state,
] = userQueryStore.useQuery(
[query]
);
......
return (
<>
<SearchButton />
......
</>
);
})
Why support React.Context store? Refer to @airma/react-state explain.
The dynamic store is a special session key collection not a real store. It persist an actual store in Provider component.
When a Provider is mounting in, it creates store, and when the provider has been unmounted, it destroys this store.
Global static store state-management
import React from 'react';
import {session} from '@airma/react-effect';
import {User} from './type';
type UserQuery = {
name: string;
username: string;
}
const userQueryStore = session(
(query: UserQuery):Promise<User[]> =>
Promise.resolve([]),
'query'
).createStore().asGlobal();
const SearchButton = ()=>{
const [
{
isFetching,
data
},
triggerQuery
] = userQueryStore.useSession();
return (
<button
disabled={isFetching}
onClick={triggerQuery}
>
query
</button>
);
}
const App = ()=>{
const [query, setQuery] = useState({name:'', username:''});
const [
state
] = userQueryStore.useQuery(
[query]
);
......
return (
<>
<SearchButton />
......
</>
);
}
The state data
from useSession is always has a undefined
union type. API useLoadedSession can be helpful if the session state.data
is not empty from initializing time.
import React from 'react';
import {session} from '@airma/react-effect';
import {User} from './type';
type UserQuery = {
name: string;
username: string;
}
const userQueryStore = session(
(query: UserQuery):Promise<User[]> =>
Promise.resolve([]),
'query'
).createStore().asGlobal();
const SearchButton = ()=>{
const [
{
isFetching,
data
},
triggerQuery
] = userQueryStore.useLoadedSession();
return (
<button
disabled={isFetching}
onClick={triggerQuery}
>
query
</button>
);
}
const App = ()=>{
const [query, setQuery] = useState({name:'', username:''});
const [
state
] = userQueryStore.useQuery(
{
variables: [query],
defaultData: []
}
);
......
return (
<>
<SearchButton />
......
</>
);
}
Want to do something when query or mutation responses?
import React from 'react';
import {session, useResponse} from '@airma/react-effect';
import {User} from './type';
type UserQuery = {
name: string;
username: string;
}
const userQuerySession = session(
(query: UserQuery):Promise<User[]> =>
Promise.resolve([]),
'query'
);
const App = ()=>{
const [query, setQuery] = useState({name:'', username:''});
const [
state
] = userQuerySession.useQuery(
[query]
);
useResponse(
(sessionState)=>{
const {
data,
isError,
error,
......
} = sessionState;
doSomething(sessionState);
},
state
);
useResponse.useSuccess(
(data, sessionState)=>{
doSomething(data);
},
state
);
useResponse.useFailure(
(error, sessionState)=>{
doSomething(error);
},
state
);
......
}
Want to run useQuery or useMutation with some features like debounce?
Strategy
import React from 'react';
import {session, Strategy} from '@airma/react-effect';
import {User} from './type';
type UserQuery = {
name: string;
username: string;
}
const userQuerySession = session(
(query: UserQuery):Promise<User[]> =>
Promise.resolve([]),
'query'
);
const App = ()=>{
const [query, setQuery] = useState({name:'', username:''});
const [
state,
trigger,
executeWithParams
] = userQuerySession.useQuery(
{
variables: [query],
strategy: Strategy.debounce(300)
}
);
......
}
The Strategy API contains some useful strategies for useQuery and useMutation. Compose some strategies together can make the session of useQuery/useMutation performance wonderfully.
import React from 'react';
import {session, Strategy} from '@airma/react-effect';
import {User} from './type';
type UserQuery = {
name: string;
username: string;
}
const userQuerySession = session(
(query: UserQuery):Promise<User[]> =>
Promise.resolve([]),
'query'
);
const App = ()=>{
const [query, setQuery] = useState({name:'', username:''});
const [
state,
trigger,
executeWithParams
] = userQuerySession.useQuery(
{
variables: [query],
strategy: [
Strategy.validate(()=>!!query.name),
Strategy.debounce(300),
Strategy.memo()
]
}
);
......
}
Want to use SWR(stale-while-revalidate)?
import React from 'react';
import {session, Strategy} from '@airma/react-effect';
import {User} from './type';
type UserQuery = {
name: string;
username: string;
}
const userQuerySession = session(
(query: UserQuery):Promise<User[]> =>
Promise.resolve([]),
'query'
);
const App = ()=>{
const [query, setQuery] = useState({name:'', username:''});
const [
state,
trigger,
executeWithParams
] = userQuerySession.useQuery(
{
variables: [query],
strategy: [
Strategy.cache({
capacity:10,
staleTime:5*60*1000
})
]
}
);
......
}
Introduce
@airma/react-effect
is an asynchronous state-management tool for react. It dependents @airma/react-state, and there are some similar apis between both packages, so, use a common package @airma/react-hooks is a better choice.
Why not use setState in asynchronous callback?
Setting state in asynchronous callback is more easy to take a stale state usage bug in code. And it often makes zombie-children problem too.
When useQuery works?
API useQuery works when it is mounted, or the dependency parameters change, just like React.useEffect performance. It also can be triggered manually.
Install and Support
The package lives in npm. To install the latest stable version, run the following command:
Install command
npm i @airma/react-effect
Browser support
chrome: '>=91',
edge: '>=91',
firefox: '=>90',
safari: '>=15'