https://github.com/filefoxper/airma
It is a method calling useReducer. It is simple for usage with more easier typescript support.
https://github.com/filefoxper/airma
closure hooks method-dispatch methods react react-context redux state state-management typescript usereducer
Last synced: 6 months ago
JSON representation
It is a method calling useReducer. It is simple for usage with more easier typescript support.
- Host: GitHub
- URL: https://github.com/filefoxper/airma
- Owner: filefoxper
- License: mit
- Created: 2022-12-02T12:26:41.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2024-12-08T03:27:55.000Z (6 months ago)
- Last Synced: 2024-12-15T15:09:03.107Z (6 months ago)
- Topics: closure, hooks, method-dispatch, methods, react, react-context, redux, state, state-management, typescript, usereducer
- Language: TypeScript
- Homepage:
- Size: 3.44 MB
- Stars: 5
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
[![npm][npm-image]][npm-url]
[![NPM downloads][npm-downloads-image]][npm-url]
[![standard][standard-image]][standard-url][npm-image]: https://img.shields.io/npm/v/%40airma/react-state.svg?style=flat-square
[npm-url]: https://www.npmjs.com/package/%40airma/react-state
[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square
[standard-url]: http://npm.im/standard
[npm-downloads-image]: https://img.shields.io/npm/dm/%40airma/react-state.svg?style=flat-square# @airma
`@airma` provides simple and useful packages for react developing env:
1. [@airma/react-state](https://filefoxper.github.io/airma/#/react-state/index)
2. [@airma/react-effect](https://filefoxper.github.io/airma/#/react-effect/index)
3. [@airma/react-hooks](https://filefoxper.github.io/airma/#/react-hooks/index)## @airma/react-state
Simple `reducer-like` state-management with method action dispatch mode for react components.
Create `reducer-like` function:
```js
export function counting(state:number){
return {
// reproduced state for render
count: `mount: ${state}`,
// action method
increase:()=>count + 1,
// action method
decrease:()=>count - 1,
// action method, define parameters freely.
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, count);
}
};
}
```Use `reducer-like` function:
```tsx
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.
### Local state management
```tsx
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: `mount: ${state}`,
increase:()=>count + 1,
decrease:()=>count - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, count);
}
};
});
......
// 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 usage with or without `React.Context` to manage a global state.
### React.Context state management
```tsx
import {memo} from 'react';
import {model} from '@airma/react-state';const countingStore = model(function counting(state:number){
return {
count: `mount: ${state}`,
increase:()=>count + 1,
decrease:()=>count - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, count);
}
};
}).createStore(0);
......
const Increase = memo(()=>{
// use store.useSelector can share state changes from store,
// when the selected result is changed it rerender component.
const increase = countingStore.useSelector(i => i.increase);
return +;
});
const Count = memo(()=>{
// use store.useModel can share state changes from store.
const {count} = countingStore.useModel();
return {count};
});
const Decrease = memo(()=>{
const decrease = countingStore.useSelector(i => i.decrease);
return -;
});
// provide store to component for a React.Context usage.
const Component = countingStore.provideTo(function Comp() {
return (
);
});
......
```Using `model(xxx).createStore().asGlobal()` can build a global store.
### Global state management
```ts
import {model} from '@airma/react-state';const countingStore = model(function counting(state:number){
return {
count: `mount: ${state}`,
increase:()=>count + 1,
decrease:()=>count - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, count);
}
};
}).createStore(0).asGlobal();
......
const Increase = memo(()=>{
const increase = countingStore.useSelector(i => i.increase);
return +;
});
const Count = memo(()=>{
const {count} = countingStore.useModel();
return {count};
});
const Decrease = memo(()=>{
const decrease = countingStore.useSelector(i => i.decrease);
return -;
});
// use global store without provider.
const Component = function Comp() {
return (
);
};
```The `useSelector` API is helpful for reducing render frequency, only when the selected result is changed, it make its owner component rerender.
### Why support context store?
In `@airma/react-state`, store is dynamic, every `provider` copies a working instance for a context usage.
That means:
1. The store data can be destroyed with its `provider` component unmount.
2. Components with same store provider can be used together in one parent component without state change effect to each other.
#### 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 async action methods
Async action often makes the problem about stale data and [zombie-children](https://react-redux.js.org/api/hooks#stale-props-and-zombie-children). So, a special tool to resolve this problem is necessary, you can try [@airma/react-effect](https://filefoxper.github.io/airma/#/react-effect/index) with it.
There are more examples, concepts and APIs in the [documents](https://filefoxper.github.io/airma/#/react-state/index) of `@airma/react-state`.
## @airma/react-effect
Simple asynchronous state-management for react. It is considered as a combined hook with `React.useEffect` and `React.useState`.
Create a callback which always returns a promise.
```ts
// parameters groupId
export function fetchUsers(groupId: number): Promise {
return userRequest.fetchUsersByGroupId(groupId);
}
```Use asynchronous callback.
```tsx
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),
// 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`.
### Local asynchronous state management
```ts
import {session} from '@airma/react-effect';const fetchUsersSession = session((groupId: number): Promise => {
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.
### React.Context asynchronous state management
```tsx
import {session} from '@airma/react-effect';// create a store for sharing state change.
const fetchUsersSession = session((groupId: number): Promise => {
return userRequest.fetchUsersByGroupId(groupId);
}, 'query').createStore();
......
const ChildQueryComponent = ()=>{
......
// when `fetchUsersSession.useQuery` works,
// the `sessionState change` is shared with a same store subscriber like `useSession`.
const [
sessionState,
recall,
recallWithVariables
] = fetchUsersSession.useQuery([props.groupId]);
......
};const ChildReceptionComponent = ()=>{
......
// the store.useSession can accept the sessionState change caused by the same store `useQuery` or `useMutation`.
const [
sessionState,
recall
] = fetchUsersSession.useSession();
......
};// provide store to component.
const Component = fetchUsersSession.provideTo(function Comp(){
return (
<>
>
);
});
```Use `session(xxx,'query'|'mutation').createStore().asGlobal()` can create a global store, which can be used directly without provider.
### Global asynchronous state management
```tsx
import {session} from '@airma/react-effect';// make store global
const fetchUsersSession = session((groupId: number): Promise => {
return userRequest.fetchUsersByGroupId(groupId);
}, 'query').createStore().asGlobal();
......
const ChildQueryComponent = ()=>{
......
const [
sessionState,
recall,
recallWithVariables
] = fetchUsersSession.useQuery([props.groupId]);
......
};const ChildReceptionComponent = ()=>{
......
const [
sessionState,
recall
] = fetchUsersSession.useSession();
......
};
// a global store API can be used directly without provider.
const Component = function Comp(){
return (
<>
>
);
};
```### API useMutation
The API `useMutation` or `(session\sessionStore).useMutation` is similar with `useQuery`.
The different is `useMutation` should be triggered to work manually.
```ts
import {session} from '@airma/react-effect';const saveUserSession = session((user: User): Promise => {
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.
### Change default trigger ways
Set `triggerOn` config property to `useQuery` or `useMutation` can change the default trigger ways of these hooks.
Make `useMutation` works when variables changes.
```ts
import {session} from '@airma/react-effect';const saveUserSession = session((user: User): Promise => {
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.
```ts
import {session} from '@airma/react-effect';const fetchUsersSession = session((groupId: number): Promise => {
return userRequest.fetchUsersByGroupId(groupId);
}, 'query');
......
const [
sessionState,
recall,
recallWithVariables
] = fetchUsersSession.useQuery({
variables: [props.groupId],
triggerOn: ['manual']
});
......
```### Strategies
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.
```ts
import {session} from '@airma/react-effect';const fetchUsersSession = session((groupId: number): Promise => {
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()]
});
......
```### Questions?
`@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](https://filefoxper.github.io/airma/#/react-effect/index) of `@airma/react-effect`.
## @airma/react-hooks
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.
```tsx
import {model, session} from '@airma/react-hooks';const countingStore = model(function counting(state:number){
return {
count: `mount: ${state}`,
increase:()=>count + 1,
decrease:()=>count - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, count);
}
};
}).createStore(0);const fetchUsersSession = session((groupId: number): Promise => {
return userRequest.fetchUsersByGroupId(groupId);
}, 'query').createStore();
......
// combine different stores together, and provide to a root component
const Component = countingStore.with(fetchUsersSession).provideTo(function Comp(){
......
});
```