npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@airma/react-effect

v18.4.2

Published

This is a react async state management tool

Downloads

245

Readme

npm NPM downloads standard

@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;
}
// Prepare a query promise callback.
const fetchUsers = (query: UserQuery):Promise<User[]> =>
        Promise.resolve([]);

const App = ()=>{
    const [query, setQuery] = useState({name:'', username:''});
    const [state, trigger, executeWithParams] = useQuery(
        // Use query callback
        fetchUsers,
        // Set parameters for query callback
        [query]
    );
    const {
        // User[] | undefined
        data,
        // boolean
        isFetching,
        // any
        error,
        // boolean
        isError,
        // boolean
        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(
        // Set mutation callback,
        // it is a promise callback.
        saveUser,
        // Set mutation parameters.
        [ user ]
    );
    const {
        // User | undefined
        data,
        // boolean
        isFetching,
        // any
        error,
        // boolean
        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;
}

// use `session` API to declare a query session
const userQuerySession = session(
    (query: UserQuery):Promise<User[]> =>
        Promise.resolve([]),
    'query'
);

const App = ()=>{
    const [query, setQuery] = useState({name:'', username:''});
    const [
        state, 
        trigger, 
        executeWithParams
        // call session.useQuery
    ] = userQuerySession.useQuery(
        // Set parameters for query callback
        [query]
    );
    const {
        // User[] | undefined
        data,
        // boolean
        isFetching,
        // any
        error,
        // boolean
        isError,
        // boolean
        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;
}

// declare a query session dynamic store
const userQueryStore = session(
    (query: UserQuery):Promise<User[]> =>
        Promise.resolve([]),
    'query'
).createStore();

const SearchButton = ()=>{
    // useSession subscribes state change from session store
    const [
        // state from session store
        {isFetching},
        // call trigger function can trigger useQuery work manually 
        triggerQuery
    ] = userQueryStore.useSession();
    return (
        <button 
            disabled={isFetching} 
            onClick={triggerQuery}
        >
        query
        </button>
    );
}

// provide dynamic store is very important
const App = userQueryStore.provideTo(()=>{
    const [query, setQuery] = useState({name:'', username:''});
    const [
        state, 
        // Write every query state change to store
    ] = 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;
}

// declare a query session global static store
const userQueryStore = session(
    (query: UserQuery):Promise<User[]> =>
        Promise.resolve([]),
    'query'
).createStore().asGlobal();

const SearchButton = ()=>{
    const [
        {
            isFetching,
            // User[] | undefined
            data
        },
        triggerQuery
    ] = userQueryStore.useSession();
    return (
        <button 
            disabled={isFetching} 
            onClick={triggerQuery}
        >
        query
        </button>
    );
}

// global static store needs no Provider.
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 = ()=>{
    // store.useLoadedSession can give out the promise resolve type without `empty`.
    const [
        {
            isFetching,
            // User[]
            data
        },
        triggerQuery
    ] = userQueryStore.useLoadedSession();
    return (
        <button 
            disabled={isFetching} 
            onClick={triggerQuery}
        >
        query
        </button>
    );
}

const App = ()=>{
    const [query, setQuery] = useState({name:'', username:''});
    const [
        state
    ] = userQueryStore.useQuery(
        // use object config to set default data
        {
            variables: [query],
            // To make `state.data` not empty,
            // a default data is needed.
            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]
    );
    
    // When useQuery/useMutation responses, 
    // useResponse calls the response callback.
    useResponse(
        // response callback
        (sessionState)=>{
            // accept a newest session state.
            const {
                data,
                isError,
                error,
                ......
            } = sessionState;
            doSomething(sessionState);
        }, 
        // listen to the session state of useQuery
        state
    );

    // When useQuery/useMutation responses successfully, 
    // useResponse.useSuccess calls the response callback.
    useResponse.useSuccess(
        (data, sessionState)=>{
            // accept a newst session state data.
            // accept a newest session state.
            doSomething(data);
        }, 
        // listen to the session state of useQuery
        state
    );

    // When useQuery/useMutation responses unsuccessfully, 
    // useResponse.useFailure calls the response callback.
    useResponse.useFailure(
        (error, sessionState)=>{
            // accept a newst session state error.
            // accept a newest session state.
            doSomething(error);
        }, 
        // listen to the session state of useQuery
        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],
            // set a debouce strategy to take debounce query feature.
            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],
            // compose different strategies.
            strategy: [
                // Validate query.name is not empty,
                // if it is empty, then stop execute query
                Strategy.validate(()=>!!query.name),
                // Query with debounce feature
                Strategy.debounce(300),
                // If the response data equals current state.data,
                // keeps current state.data.
                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: [
                // use swr 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'