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

reasync-hooks

v0.1.1

Published

A library to help developer keep track of redux async action states.

Downloads

1

Readme

Reasync-hooks

GitHub license npm version CircleCI

A library to keep track of redux async action states, based on react-redux and react hooks.

Table of Contents

Installation

React Redux Async Hooks requires React 16.8.3 and React-redux 7.10 or later.

npm install --save reasync-hooks

This assumes that you’re using npm package manager with a module bundler like Webpack or Browserify to consume CommonJS modules.

Example

Basis example

You can play around with the following example in this codesandbox:

Step 1

Create store
import { applyMiddleware, combineReducers, createStore, compose } from "redux";
import { asyncReduxMiddlewareCreator , asyncStateReducer } from "reasync-hooks";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const asyncReduxMiddleware = asyncReduxMiddlewareCreator();
const rootReducer = combineReducers({
  asyncState: asyncStateReducer
});
const store = createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(asyncReduxMiddleware))
);

Step 2

Create async actions
import { asyncActionCreator } from "reasync-hooks";

const FULFILLED_ACTION = "FULFILLED_ACTION";
const REJECTED_ACTION = "REJECTED_ACTION";
const asyncFulfilledAction = asyncActionCreator(
  FULFILLED_ACTION,
  //Return a fulfilled Promise
  () =>
    new Promise(function(resolve) {
      setTimeout(function() {
        resolve("");
      }, 1000);
    })
);
const asyncRejectedAction = asyncActionCreator(
  REJECTED_ACTION,
  //Return a rejected Promise
  () =>
    new Promise(function(resolve, reject) {
      setTimeout(function() {
        reject("");
      }, 1000);
    })
);

Step 3

Use hooks in your component
import {
  useIsAsyncPendingSelector,
  useOnAsyncFulfilled,
  useOnAsyncRejected
} from "reasync-hooks";
import { useDispatch } from "react-redux";
import { Button, message } from "antd";

const BasisExample = () => {
  const dispatch = useDispatch();
  const isFulfilledActionPending = useIsAsyncPendingSelector([
    FULFILLED_ACTION
  ]);
  const isRejectedActionPending = useIsAsyncPendingSelector([REJECTED_ACTION]);
  //Notify something when async action is from pending to fulfilled
  useOnAsyncFulfilled([FULFILLED_ACTION], asyncType => {
    message.success(asyncType);
  });
  //Notify something when async action is from pending to rejected
  useOnAsyncRejected([REJECTED_ACTION], asyncType => {
    message.error(asyncType);
  });
  return (
    <div className="App">
      <Button
        onClick={() => dispatch(asyncFulfilledAction)}
        loading={isFulfilledActionPending}
        type="primary"
      >
        asyncFulfilledAction
      </Button>
      <Button
        onClick={() => dispatch(asyncRejectedAction)}
        loading={isRejectedActionPending}
        type="danger"
      >
        asyncRejectedAction
      </Button>
    </div>
  );
};

Step 4

Nest the component inside of a <Provider>
import { Provider } from "react-redux";

const App = () => (
  <Provider store={store}>
    <BasisExample />
  </Provider>
);
export default App;

Complete example:

import React from "react";
import { applyMiddleware, combineReducers, createStore, compose } from "redux";
import {
  asyncReduxMiddlewareCreator,
  asyncStateReducer,
  useIsAsyncPendingSelector,
  useOnAsyncFulfilled,
  useOnAsyncRejected,
  asyncActionCreator
} from "reasync-hooks";
import { Provider, useDispatch } from "react-redux";
import { Button, message } from "antd";
import("antd/dist/antd.css");
import ("./App.css");

/*
Step 1: create store
 */
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const asyncReduxMiddleware = asyncReduxMiddlewareCreator();
const rootReducer = combineReducers({
  asyncState: asyncStateReducer
});
const store = createStore(
    rootReducer,
    composeEnhancers(applyMiddleware(asyncReduxMiddleware))
);

/*
Step 2: create async actions
 */
const FULFILLED_ACTION = "FULFILLED_ACTION";
const REJECTED_ACTION = "REJECTED_ACTION";
const asyncFulfilledAction = asyncActionCreator(
    FULFILLED_ACTION,
    //Return a fulfilled Promise
    () =>
        new Promise(function(resolve) {
          setTimeout(function() {
            resolve("");
          }, 1000);
        })
);
const asyncRejectedAction = asyncActionCreator(
    REJECTED_ACTION,
    //Return a rejected Promise
    () =>
        new Promise(function(resolve, reject) {
          setTimeout(function() {
            reject("");
          }, 1000);
        })
);

/*
Step 3: use hooks in your component
 */
const BasisExample = () => {
  const dispatch = useDispatch();
  const isFulfilledActionPending = useIsAsyncPendingSelector([
    FULFILLED_ACTION
  ]);
  const isRejectedActionPending = useIsAsyncPendingSelector([REJECTED_ACTION]);
  //Notify something when async action is from pending to fulfilled
  useOnAsyncFulfilled([FULFILLED_ACTION], asyncType => {
    message.success(asyncType);
  });
  //Notify something when async action is from pending to rejected
  useOnAsyncRejected([REJECTED_ACTION], asyncType => {
    message.error(asyncType);
  });
  return (
      <div className="App">
        <Button
            onClick={() => dispatch(asyncFulfilledAction)}
            loading={isFulfilledActionPending}
            type="primary"
        >
          asyncFulfilledAction
        </Button>
        <Button
            onClick={() => dispatch(asyncRejectedAction)}
            loading={isRejectedActionPending}
            type="danger"
        >
          asyncRejectedAction
        </Button>
      </div>
  );
};

/*
Step4: nest the component inside of a `<Provider>`
 */
const App = () => (
    <Provider store={store}>
      <BasisExample />
    </Provider>
);
export default App;

Advanced example: fetch data and handle error

There are two examples. One uses the reasync-hooks, the other only uses hooks. Both examples implement the same goal that fetch data and, if successful, notify the data, otherwise notify the error message.

1.Only use react hooks

Note: This example is only used to help users understand the usage of reasync-hooks and is not recommended.

import React, { useEffect, useState } from "react";
import {Button,message} from 'antd'

const ExampleOnlyUseHooks = () => {
  //Mock that fetch data successfully
  const fetchDataSuccess = () =>
      new Promise(function(resolve) {
        setTimeout(function() {
          //Receive data
          resolve({ profile: { email: "[email protected]" } });
        }, 1000);
      });

  //Mock that an error occurs when fetch data
  const fetchDataError = () =>
      new Promise(function(resolve, reject) {
        setTimeout(function() {
          //An error occurs
          reject({ msg: "something wrong" });
        }, 1000);
      });

  const [isFulfilledAsyncPending, setIsFulfilledAsyncPending] = useState(false);
  const [data, setSetData] = useState();

  const [isRejectedAsyncPending, setIsRejectedAsyncPending] = useState(false);
  const [error, setError] = useState();

  useEffect(() => {
    if (data) message.success(data.profile.email);
  }, [data]);

  useEffect(() => {
    if (error) message.error(error.msg);
  }, [error]);

  return (
      <div className="App">
        <Button
            onClick={() => {
              setIsFulfilledAsyncPending(true);
              fetchDataSuccess().then(data => {
                setIsFulfilledAsyncPending(false);
                setSetData(data);
              });
            }}
            loading={isFulfilledAsyncPending}
            type='primary'
        >
          asyncFulfilledAction
        </Button>
        <Button
            onClick={() => {
              setIsRejectedAsyncPending(true);
              fetchDataError().catch(error => {
                setIsRejectedAsyncPending(false);
                setError(error);
              });
            }}
            loading={isRejectedAsyncPending}
            type='danger'
        >
          asyncRejectedAction
        </Button>
      </div>
  );
};

2.Use reasync-hooks

You can play around with the following example in this codesandbox:

Step 1

Customize the redux middleware
import { asyncReduxMiddlewareCreator , asyncStateReducer } from "reasync-hooks";

const fulfilledHandler = (resolveValue, action, dispatch) => {
  dispatch({ ...action, data: resolveValue });
};
const rejectedHandler = (rejectedReason, action, dispatch) => {
  dispatch({
    ...action,
    error: rejectedReason
  });
};
const asyncReduxMiddleware = asyncReduxMiddlewareCreator(
  fulfilledHandler,
  rejectedHandler
);

Step 2

Create async actions
import { asyncStateReducer, asyncActionCreator } from "reasync-hooks";

const FULFILLED_ACTION = "FULFILLED_ACTION";
const REJECTED_ACTION = "REJECTED_ACTION";
//Mock that fetch data successfully
const fetchDataSuccess = () =>
  new Promise(function(resolve) {
    setTimeout(function() {
      //Receive data
      resolve({ profile: { email: "[email protected]" } });
    }, 1000);
  });
//Mock that an error occurs when fetch data
const fetchDataError = () =>
  new Promise(function(resolve, reject) {
    setTimeout(function() {
      //An error occurs
      reject({ msg: "something wrong" });
    }, 1000);
  });
const asyncFulfilledAction = asyncActionCreator(
  FULFILLED_ACTION,
  fetchDataSuccess
);
const asyncRejectedAction = asyncActionCreator(REJECTED_ACTION, fetchDataError);

Step 3

Create reducers

Note: To reduce boilerplate, you can use createReducer.

import {
  fulfilledTypeCreator,
  rejectedTypeCreator
} from "reasync-hooks";

const fulfilledReducer = (state = {}, action) => {
  if (action.type === fulfilledTypeCreator(FULFILLED_ACTION)) {
    return {
      ...state,
      ...action.data
    };
  }
  return state;
};
const errorReducer = (state = {}, action) => {
  if (action.type === rejectedTypeCreator(REJECTED_ACTION))
    return {
      ...state,
      [REJECTED_ACTION]: action.error
    };
  return state;
};

Step 4

Create store
import { applyMiddleware, combineReducers, createStore, compose } from "redux";
import { asyncReduxMiddlewareCreator , asyncStateReducer } from "reasync-hooks";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const rootReducer = combineReducers({
  user: fulfilledReducer,
  error: errorReducer,
  asyncState: asyncStateReducer
});
export const store = createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(asyncReduxMiddleware))
);

Step 5

Use hooks in your component
import {
  useIsAsyncPendingSelector,
  useOnAsyncFulfilled,
  useOnAsyncRejected
} from "reasync-hooks";
import { useDispatch, useStore } from "react-redux";
import { Button, message } from "antd";

const AdvancedExample = () => {
  const dispatch = useDispatch();
  const store = useStore();
  const isFulfilledActionPending = useIsAsyncPendingSelector([
    FULFILLED_ACTION
  ]);
  const isRejectedActionPending = useIsAsyncPendingSelector([REJECTED_ACTION]);
  //Notify data when async action changes from pending to fulfilled
  useOnAsyncFulfilled([FULFILLED_ACTION], () => {
    message.success(store.getState().user.profile.email);
  });
  //Notify error message when async action is from pending to rejected
  useOnAsyncRejected([REJECTED_ACTION], asyncType => {
    message.error(store.getState().error[asyncType].msg);
  });
  return (
    <div className="App">
      <Button
        onClick={() => dispatch(asyncFulfilledAction)}
        loading={isFulfilledActionPending}
        type="primary"
      >
        asyncFulfilledAction
      </Button>
      <Button
        onClick={() => dispatch(asyncRejectedAction)}
        loading={isRejectedActionPending}
        type="danger"
      >
        asyncRejectedAction
      </Button>
    </div>
  );
};

Step 6

Nest the component inside of a <Provider>
import { Provider } from "react-redux";

const App = () => (
  <Provider store={store}>
    <AdvancedExample />
  </Provider>
);
export default App;

complete example:

import React from "react";
import { applyMiddleware, combineReducers, createStore, compose } from "redux";
import {
  fulfilledTypeCreator,
  rejectedTypeCreator,
  asyncReduxMiddlewareCreator,
  asyncStateReducer,
  useIsAsyncPendingSelector,
  useOnAsyncFulfilled,
  useOnAsyncRejected,
  asyncActionCreator
} from "reasync-hooks";
import { Provider, useDispatch, useStore } from "react-redux";
import { Button, message } from "antd";
import("antd/dist/antd.css");
import("./App.css");

/*
Step 1: customize the redux  middleware
 */
const fulfilledHandler = (resolveValue, action, dispatch) => {
  dispatch({ ...action, data: resolveValue });
};
const rejectedHandler = (rejectedReason, action, dispatch) => {
  dispatch({
    ...action,
    error: rejectedReason
  });
};
const asyncReduxMiddleware = asyncReduxMiddlewareCreator(
    fulfilledHandler,
    rejectedHandler
);

/*
Step 2: create async actions
*/
const FULFILLED_ACTION = "FULFILLED_ACTION";
const REJECTED_ACTION = "REJECTED_ACTION";
//Mock that fetch data successfully
const fetchDataSuccess = () =>
    new Promise(function(resolve) {
      setTimeout(function() {
        //Receive data
        resolve({ profile: { email: "[email protected]" } });
      }, 1000);
    });
//Mock that an error occurs when fetch data
const fetchDataError = () =>
    new Promise(function(resolve, reject) {
      setTimeout(function() {
        //An error occurs
        reject({ msg: "something wrong" });
      }, 1000);
    });
const asyncFulfilledAction = asyncActionCreator(
    FULFILLED_ACTION,
    fetchDataSuccess
);
const asyncRejectedAction = asyncActionCreator(REJECTED_ACTION, fetchDataError);

/*
Step 3: create reducers
 */
const fulfilledReducer = (state = {}, action) => {
  if (action.type === fulfilledTypeCreator(FULFILLED_ACTION)) {
    return {
      ...state,
      ...action.data
    };
  }
  return state;
};
const errorReducer = (state = {}, action) => {
  if (action.type === rejectedTypeCreator(REJECTED_ACTION))
    return {
      ...state,
      [REJECTED_ACTION]: action.error
    };
  return state;
};

/*
 Step 4: create store
 */
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const rootReducer = combineReducers({
  user: fulfilledReducer,
  error: errorReducer,
  asyncState: asyncStateReducer
});
export const store = createStore(
    rootReducer,
    composeEnhancers(applyMiddleware(asyncReduxMiddleware))
);

/*
Step 5: use hooks in your component
 */
const AdvancedExample = () => {
  const dispatch = useDispatch();
  const store = useStore();
  const isFulfilledActionPending = useIsAsyncPendingSelector([
    FULFILLED_ACTION
  ]);
  const isRejectedActionPending = useIsAsyncPendingSelector([REJECTED_ACTION]);
  //Notify data when async action changes from pending to fulfilled
  useOnAsyncFulfilled([FULFILLED_ACTION], () => {
    message.success(store.getState().user.profile.email);
  });
  //Notify error message when async action is from pending to rejected
  useOnAsyncRejected([REJECTED_ACTION], asyncType => {
    message.error(store.getState().error[asyncType].msg);
  });
  return (
      <div className="App">
        <Button
            onClick={() => dispatch(asyncFulfilledAction)}
            loading={isFulfilledActionPending}
            type="primary"
        >
          asyncFulfilledAction
        </Butto
        <Button
            onClick={() => dispatch(asyncRejectedAction)}
            loading={isRejectedActionPending}
            type="danger"
        >
          asyncRejectedAction
        </Button>
      </div>
  );
};

/*
Step 6: nest the component inside of a `<Provider>
 */
const App = () => (
    <Provider store={store}>
      <AdvancedExample />
    </Provider>
);
export default App;

API

asyncActionCreator

const asyncAction = () => actionTypeCreator(type, asyncAction, extraArgument)

Parameters

actionType:string : An async action type.

asyncFunction:(getState)=>Promise: A function that executes an asynchronous operation.

extraArgument?:any : A custom argument that will be available by action.extraArgument in the async action workflow.

Return

asyncAction:{types:[pendingType,fulfilledType,rejectedType],asyncFunction,extraArgument}

The reduxMiddleware that is created by asyncReduxMiddlewareCreator will only response the action with a types property . In fact, the idea behind react-redux-async-hooks is that dispatch a corresponding action(pendingType, fulfilledType, rejectedType) when the Promise that asyncFunction returns is in a different state(pending,fulfilled,rejected).

Note: asyncFunction must be a function that returns a Promise.

asyncReduxMiddlewareCreator

const asyncReduxMiddleware = asyncReduxMiddleware(fullfilledHandler, rejectedHandler)

Parameters

fullfilledHandler?:(resolveValue, action, dispatch, getState) => void: If the promise has already been fulfilled, this handler will be called.

rejectedHandler?:( rejectedReason, action, dispatch, getState) => void: If the promise has already been rejected, this handler will be called.

Note: Default handlers only call diapatch(action).

Return

void

Customize your asyncrReduxMiddeware.

asyncStateReducer

A reducer that specifies how the application's state changes in response to async action to the store.

useIsAsyncPendingSelector

const isPending = useIsAsyncPendingSelector(actionTypes, asyncStateReducerKey)

Parameters

actionTypes:string[]: A group of async actions that are kept track of.

asyncStateReducerKey:string="asyncState" : Under the hood, useIsAsyncPendingSelector tries to get async action states by

//https://react-redux.js.org/api/hooks#useselector
useSelector(state => state[asyncStateReducerKey]);

So you have to ensure asyncStateReducerKey same with the key that is passed to combinReducers for asyncSateReducer .

Return

isAsyncPending:boolen: True means that at least one among asyncTypes is in pending . False means that all in asyncTypes are in fulfilled or rejected.

useOnAsyncFulfilled

useOnAsyncFulfilled(actionTypes, handler, asyncStateReducerKey)

Parameters

actionTypes:string[]: A group of async actions that are kept track of.

handler:(asyncType)=>void: Run when any one of actionTypes changes from pending to fulfilled. The asyncType is passed to handler is the one that triggers the handler.

asyncStateReducerKey:string="asyncState" : Same with this parameter in useIsAsyncPendingSelector.

Return

void

useOnAsyncRejected

useOnAsyncRejected(actionTypes, handler, asyncStateReducerKey)

Parameters

actionTypes:string[]: A group of async action types that are kept track of.

handler:(actionType)=>void: Run when one of actionTypes changes from pending to rejected. The actionType is passed to handler is the one that triggers the handler.

asyncStateReducerKey="asyncState": Same with this parameter in useIsAsyncPendingSelector.

Return

void

fulfilledTypeCreator

const fulfilledType = fulfilledTypeCreator(actionType)

Parameters

actionType:string: An action type that represents an async action.

Return

asyncFulfilledType:string: An async action type that you can use in your reducers to catch up the async action when it is in fulfilled.

rejectedTypeCreator

const rejectedType = rejectedTypeCreator(actionType)

Parameters

actionType:string: An action type that represents an async action.

Return

asyncFulfilledType:string: An async action type that you can use in your reducers to catch up the async action when it is in rejected.

Todo

  • [x] Add test

License

[MIT]