Security News
PyPI’s New Archival Feature Closes a Major Security Gap
PyPI now allows maintainers to archive projects, improving security and helping users make informed decisions about their dependencies.
@airma/react-effect
Advanced tools
@airma/react-effect
is designed for managing the asynchronous effect state for react components.
React hook system is designed for synchronous render usage. But asynchronous operations are often used in components, so, take the asynchronous code out of render is a good choice. That's why useEffect
is useful in hook system, we can set the asynchronous in it, and run it out of the render time.
Good example:
import {useEffect, useState} from 'react';
import {query} from './service';
const useQueryEffect = (variables)=>{
const [data, setData] = useState(undefined);
const [isFetching, setFetching] = useState(false);
const [error, setError] = useState(undefined);
useEffect(()=>{
setFetching(true);
// limit the asynchronous only in useEffect
query(variables).then((d)=>{
// set the query result into state
setData(d);
setError(undefined);
setFetching(false);
},(e)=>{
setError(e);
setFetching(false);
});
},[variables]); // when variables change, run query
// return state information out for render usage
return {data, isFetching, error};
};
const App = memo(()=>{
......
const {data, isFetching, error} = useQueryEffect(variables);
return ......;
});
Not so good example:
import {memo, useState} from 'react';
import {query} from './service';
const App = memo(()=>{
const [data, setData] = useState(undefined);
const [isFetching, setFetching] = useState(false);
const [error, setError] = useState(undefined);
// Use asynchronous callback directly
// may affect the other operation codes,
// and make asynchronous operations spread out in component
const handleQuery = async (variables)=>{
setFetching(true);
try {
const d = await query(variables);
setData(d);
setError(undefined);
} catch(e) {
setError(e);
} finally {
setFetching(false);
}
};
// affected by asynchronous callback `handleQuery`
const handleReset = async ()=>{
await handleQuery(defaultVariables);
doSomething();
};
useEffect(()=>{
handleQuery();
},[]);
return ......;
})
Use asynchronous callback all over in component is not a good idea, we need concentrative controllers for limit asynchronous operations in less effects. Then we can have a simple synchronously render component.
Now, Let's take some simple, but more powerful API to replace the code of good example above.
We can wrap a promise return callback to useQuery
, when the dependecy varaibles change, it fetches data for you.
import React from 'react';
import {useQuery} from '@airma/react-effect';
import {client} from '@airma/restful';
type UserQuery = {
name: string;
username: string;
}
const cli = client();
const App = ()=>{
const [query, setQuery] = useState({name:'', username:''});
const [result, execute] = useQuery(
// query method
(q: UserQuery)=>
cli.rest('/api/user/list').
setParams(q).
get<User[]>(),
// dependency vairables change,
// the query method runs with the newest variables.
[query]
);
const {
// User[] | undefined
data,
// boolean
isFetching,
// any
error,
// boolean
isError
} = result;
......
}
If you want to execute the query manually, you can set manual:true
to the config.
import React from 'react';
import {useQuery} from '@airma/react-effect';
import {client} from '@airma/restful';
const cli = client();
const App = ()=>{
const [query, setQuery] = useState({name:'', username:''});
const [result, execute] = useQuery(
// query method
()=>
cli.rest('/api/user/list').
setParams(query).
get<User[]>(),
// set manual execution,
// now, you need to call `execute` to query data.
{manual: true}
);
const {
// User[] | undefined
data,
// boolean
isFetching,
// any
error,
// boolean
isError
} = result;
const handleClick = async ()=>{
const {
// User[] | undefined
data,
// boolean
isFetching,
// any
error,
// boolean
isError,
// boolean
// the result might be abandoned,
// if the execution is not the newest one.
abandon
} = await execute();
}
......
}
The manual execution is not recommend, you may accept an abandoned result, if the execution is not the newest one, in that case, you may have a different result with the hook useQuery
result. And we have talked the problem about asynchronous code spread out in component.
To execute a mutation, you can use useMutation
. It only can be executed manually.
import React from 'react';
import {useMutation} from '@airma/react-effect';
import {client} from '@airma/restful';
const cli = client();
const App = ()=>{
const [user, setUser] = useState({name:'', username:''});
const [result, execute] = useMutation(
// mutation method
(u:User)=>
cli.rest('/api/user').
setBody(u).
post<User>(),
// set variables
[user]
);
const {
// User | undefined
data,
// boolean
isFetching,
// any
error,
// boolean
isError
} = result;
const handleClick = ()=>{
// it returns a promise result,
// but we recommoned you using it as a void returning callback
execute();
}
......
}
The different with useQuery
is that the useMutation
can not be truly executed again if the last execution is not finished, it returns the last result for you.
Sometimes we need an mutation only can be executed once. We can take a Strategy
like Strategy.once
.
import React from 'react';
import {useMutation, Strategy} from '@airma/react-effect';
import {client} from '@airma/restful';
const cli = client();
const App = ()=>{
const [user, setUser] = useState({name:'', username:''});
const [result, execute] = useMutation(
// mutation method
(u:User)=>
cli.rest('/api/user').
setBody(u).
post<User>(),
// set strategy
{
variables: [user],
strategy: Strategy.once()
}
);
const {
// User | undefined
data,
// boolean
isFetching,
// any
error,
// boolean
isError
} = result;
const handleClick = async ()=>{
const {
// User | undefined
data,
// boolean
isFetching,
// any
error,
// boolean
isError,
// boolean
abandon
} = await execute();
}
......
}
We have provides a EffectProvider
for sharing the state changes of useQuery
and useMutation
.
import React, {memo} from 'react';
import { client } from '@airma/restful';
import { useModel, useSelector, factory } from '@airma/react-state';
import { EffectProvider, asyncEffect, useAsyncEffect } from '@airma/react-effect';
type UserQuery = {name: string, username: string};
const cli = client();
const userQueryModel = (state: UserQuery)=>{
const {name, username} = state;
return {
name,
username,
state,
changeName(e: ChangeEvent){
return {username, name: e.target.value};
},
changeUsername(e: ChangeEvent){
return {name, username: e.target.value};
}
}
}
const queryUsers = (query:UserQuery)=> cli.rest('/api/user/list').
setParams(query).
get<User[]>();
const models = {
userQuery: factory(userQueryModel), // make a customized model key
queryUsers: asyncEffect(queryUsers) // make an effect model key
};
const Condition = memo(()=>{
const {
name,
username,
changeName,
changeUsername
} = useModel(models.userQuery);
// useAsyncEffect can accept the query state changes
// from `models.queryUsers`,
// it also can trigger it query again by `trigger`.
const [{isFetching}, trigger] = useAsyncEffect(models.queryUsers);
return (
<div>
<input type="text" value={name} onChange={changeName}/>
<input
type="text"
value={username}
onChange={changeUsername}
/>
{/* we disable query button, */}
{/* when the query is fetching */}
<button
disabled={isFetching}
onClick={trigger}
>
query
</button>
</div>
)
});
const Datasource = memo(()=>{
const q = useSelector(models.userQuery,s =>s.state);
const [
{
data,
isFetching,
error,
isError
}
] = useQuery(models.queryUsers, [q]);
return ......;
});
const App = memo(()=>{
// yes,
// EffectProvider is just the `ModelProvider` in
// `@airma/react-state`,
// you can choose any of them as you wish.
return (
<EffectProvider value={models}>
<Condition/>
<Datasource/>
</EffectProvider>
);
})
Now, you can share the query or mutation state any where in a EffectProvider
. Because the EffectProvider
is ModelProvider
, so, they have same features, for example, the useQuery or useAsynEffect find the key in parent Providers, the middle Provider will not block them. You can refer to ModelProvider in @airma/react-state.
The promise result is a unitary result format for both useQuery and useMutation.
export declare type PromiseResult<T> = {
// the promise result of the callback,
// you provided for useQuery or useMutation
data: T | undefined;
// error of the promise
error?: any;
// is some errors happens,
// it is marked when the promise is rejected.
isError: boolean;
// if the execution is not finished, it is true,
// otherwise, it is false
isFetching: boolean;
// is the result is abandoned
abandon: boolean;
};
To execute a query promise callback.
function useQuery<
D extends PromiseEffectCallback<any> | ModelPromiseEffectCallback<any>
>(
callback: D,
config?: QueryConfig<PCR<D>, MCC<D>> | Parameters<MCC<D>>
): [PromiseResult<PCR<D>>, () => Promise<PromiseResult<PCR<D>>>];
parameters:
effect model
, the query result will be shared out to any place in a EffectProvider.config:
true
, means you want to execute the query manually, then the deps and variables change will not affect the query callback running.debounce
, once
. If it is an array, the query follows running order from outside to inside.EffectConfigProvider
config.returns:
[
result,
execute
]
To execute a mutation promise callback, it can only be drived manually by calling the returning method execute
.
function useMutation<
D extends PromiseEffectCallback<any> | ModelPromiseEffectCallback<any>
>(
callback: D,
config?: MutationConfig<PCR<D>, MCC<D>> | Parameters<MCC<D>>
): [PromiseResult<PCR<D>>, () => Promise<PromiseResult<PCR<D>>>];
parameters:
effect model
, the query result will be shared out to any place in a EffectProvider.config:
debounce
, once
. If it is an array, the query follows running order from outside to inside.EffectConfigProvider
config.returns:
[
result,
execute
]
It is used to generate a effect model
with effect( promise ) callback. We can provide it as a key to EffectProvider
or ModelProvider in @airma/react-state
for state sharing. And use useQuery
or useMutation
to link it, and fetching the query state.
function asyncEffect<
E extends (...params: any[]) => Promise<any>,
T = E extends (...params: any[]) => Promise<infer R> ? R : never
>(effectCallback: E): ModelPromiseEffectCallback<E>;
parameters:
returns
A react-state factory model with effect( promise ) callback.
It is used to accept the state change from useQuery
or useMutation
with a same effect model
.
function useAsyncEffect<
D extends ModelPromiseEffectCallback<any>
>(effectModel: D): [PromiseResult<PCR<D>>, () => void];
parameters:
effect model
created by asyncEffect
API.returns:
[
result,
trigger
]
The trigger method is different with execute
method returned by useQuery
and useMutation
. It returns void, that means it can not be await
.
You can refer it to ModelProvider in @airma/react-state
.
You can refer it to withModelProvider in @airma/react-state
.
It is a react Provider
for setting global config for every useQuery
and useMutation
in children
, it can be ignored by the local config option exact
.
import {
EffectConfigProvider,
Strategy,
useQuery
} from '@airma/react-effect';
import type {EffectConfig} from '@airma/react-effect';
// The EffectConfig only support rebuild strategy currently.
const config: EffectConfig = {
// The strategy is a callback,
// it accepts a running effect strategy array,
// and a effect type: 'query' | 'mutation'.
// You can complete the running strategies
// with padding strategies,
// so, the running effect will work with these new strategies.
// It can be ignored by a local effect config option:
// `exact: true`
strategy:(
s:(StrategyType | undefined| null)[], type: 'query' | 'mutation'
)=>[...s, Strategy.error((e)=>console.log(e))]
}
const App = ()=>{
// if the `fetchUsers` is failed,
// the global config strategy `Strategy.error` works.
useQuery(fetchUsers, [data]);
useQuery(fetchGroups, {
variables: [...ids],
strategy: [
Strategy.debounce(300),
Strategy.error(...)
],
// tell useQuery to use the current config exactly.
exact: true
});
......
}
......
{/* Set a EffectConfig */}
<EffectConfigProvider
value={Strategy.error(e => console.log(e))}
>
</EffectConfigProvider>
It provides some useful effect running Strategy
for you.
const Strategy:{
debounce: (op: { time: number } | number) => StrategyType,
once: () => StrategyType
error: (
process: (e: unknown) => any,
option?: { withAbandoned?: boolean }
) => StrategyType;
};
You can use it to the config strategy
in useQuery
and useMutation
.
For example:
import {
Strategy,
useQuery
} from '@airma/react-effect';
const useUserList = (...ids:number[])=>{
useQuery(fetchUsers, {
variables: [...ids],
strategy: [
Strategy.debounce(300),
Strategy.error((e) =>console.log(e))
]
})
};
you can set a debounce running time to it. like:
useQuery(callback,{
variables:[...],
strategy: Strategy.debounce({time:300})
})
Then the query callback runs with this debounce strategy.
It is used to force the query or mutation callback only runs once, if no error comes out.
You can set a callback to process the error information from promise rejection.
Use it as a global effect config strategy can help you reduce the codes for dealing a common error process.
import {
Strategy,
EffectConfigProvider
} from '@airma/react-effect';
const error = Strategy.error((e) =>console.log(e));
const config = {
strategy: (s)=>[...s, error]
}
<EffectConfigProvider value={config}>
......
</EffectConfigProvider>
By the default, it only process the error result which is not abandoned. You can set {withAbandoned: true}
for dealing includes the abandoned errors.
import {
Strategy,
EffectConfigProvider
} from '@airma/react-effect';
const error = Strategy.error(
(e) =>console.log(e),
{withAbandoned: true}
);
const config = {
strategy: (s)=>[...s, error]
}
<EffectConfigProvider value={config}>
......
</EffectConfigProvider>
You can write Strategy yourself, it is a simple work.
export type StrategyType<T = any> = (
getCurrentState: () => PromiseResult<T>,
runner: () => Promise<PromiseResult<T>>,
storeRef: { current: any }
) => Promise<PromiseResult<T>>;
A Strategy function accepts a parameter with properties:
For example:
function once(): StrategyType {
// this inner function is a Strategy
return function oc(value: {
current: () => PromiseResult;
runner: () => Promise<PromiseResult>;
store: { current?: boolean };
}) {
const { current, runner, store } = value;
// It store a boolean value to tell
// if the effect callback is started.
// If this value is true,
// it returns a current state promise,
// and mark it to abandoned.
if (store.current) {
return new Promise(resolve => {
// use current callback to fetch the current data,
// which is returned by the useQuery
const currentState = current();
resolve({ ...currentState, abandon: true });
});
}
// If the store value is false,
// it marks it as started,
// then truely start it.
store.current = true;
return runner().then(d => {
if (d.isError) {
// if the promise is error,
// mark it to false again,
// the the effect callback can be started again.
store.current = false;
}
return d;
});
};
};
FAQs
This is a react async state management tool
The npm package @airma/react-effect receives a total of 19 weekly downloads. As such, @airma/react-effect popularity was classified as not popular.
We found that @airma/react-effect demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
PyPI now allows maintainers to archive projects, improving security and helping users make informed decisions about their dependencies.
Research
Security News
Malicious npm package postcss-optimizer delivers BeaverTail malware, targeting developer systems; similarities to past campaigns suggest a North Korean connection.
Security News
CISA's KEV data is now on GitHub, offering easier access, API integration, commit history tracking, and automated updates for security teams and researchers.