react-redux-loading-hook

This package provides the tools for managing loading state on asynchronous operations in Redux and to retrieve that state via a React hook. This is useful for when your app is, for example, fetching data from an API and you want to indicate this to your user by showing or hiding content.

Usage no npm install needed!

<script type="module">
  import reactReduxLoadingHook from 'https://cdn.skypack.dev/react-redux-loading-hook';
</script>

README

React-Redux Loading Hook

This package provides the tools for managing loading state on asynchronous operations in Redux and to retrieve that state via a React hook. This is useful for when your app is, for example, fetching data from an API and you want to indicate this to your user by showing or hiding content.

The package uses Redux for managing the loading state and works well with packages like redux-observable that rely on pairs of Redux actions for starting and handling the results of asynchronous operations.

Example

Check the example in the example directory, which is a basic Next.js application showcasing this package's functionality. It uses redux-observable for handling AJAX requests.

Installation

npm install react-redux-loading-hook

or

yarn add react-redux-loading-hook

Usage

First you will need to configure your Redux store by adding a reducer that stores the loading state and by adding a middleware that dispatches actions for setting the state. Both are provided by this package.

import { createLoadingMiddleware, loadingStateReducer } from "react-redux-loading-hook";
import { createStore, applyMiddleware } from "redux";

// combine the loadingStateReducer with your other reducers
const rootReducer = combineReducers({
    loadingState: loadingStateReducer,
    ...
});

// create a selector function for getting the loading state from the store;
// we're exporting it because we need it elsewhere (see below)
export const getLoadingState = (state) => state.loadingState;

// create the middleware; it needs to know where the loading state is stored, so pass in
// the selector
const loadingMiddleware = createLoadingMiddleware(getLoadingState);

// create the store, passing in the root reducer and the middleware
const store = createStore(rootReducer, applyMiddleware(loadingMiddleware));

This package does not actually export a hook, but rather a function that creates a hook. Similar to the middleware, the hook also needs to know where the loading state is stored.

// the selector function for getting the loading state from the store (see above)
import { getLoadingState } from "...";

// create the hook, passing in the selector, and export it for use in your components
export const useLoading = createUseLoading(getLoadingState);

Now you're ready to use the hook. The useLoading hook (or whatever you named it) takes one parameter: a loading module. A loading module defines an array of action triplets for which the loading state should be retrieved.

An action triplet is an array that contains three Redux action type names. The first is the 'start action', which, when dispatched, sets the loading state to true. The other two are the 'success action' and 'error action' respectively, which, when dispatched, set the loading state back to false.

The loading module also needs a unique name, which is used internally.

import React from "react";
import { useLoading } from "...";

const loadingModule = new LoadingModule({
    name: "example",
    actionTriplets: [
        ["startAction", "successAction", "errorAction"],
        ...
    ]
});

const LoadingText = () => {
    const isLoading = useLoading(loadingModule);

    return isLoading ? <span>Loading...</span> : <span>Not loading!</span>;
};

Now when the Redux action of type startAction is dispatched, the useLoading hook will return true and the LoadingText component will render Loading.... When the asynchronous operation is finished, either successfully or with an error, and the appropriate action (either successAction or errorAction) is dispatched, the state is set back to false and the component will render Not loading!.

Options

Default error actions per instance

Loading modules can be created with a default error action. This is useful when you want to handle multiple error cases by dispatching the same Redux action. To do this, set the defaultErrorAction property on the config object passed into the loading module's constructor and omit the third element (the error action) in your action triplets:

const loadingModule = new LoadingModule({
    name: "example",
    actionTriplets: [
        ["startAction", "successAction"],
        ...
    ],
    defaultErrorAction: "errorAction"
});

Note that it's possible to use both explicitly set error actions and a default error action in one loading module. Explicitly set error actions take precendence over the default error action, so you can, for example, use the default for all but one action triplet, should you want to.

Global default error action

Alternatively, you can also set a default error action for all loading modules in one go. Import the LoadingModule object and set its defaultErrorAction static property. The type you set here will be used if you set neither an explicit error action nor a default error action during instance creation.

import { LoadingModule } from "react-redux-loading-hook";

LoadingModule.defaultErrorAction = "errorAction";

Note that you must ensure that the code that sets the static property runs before the LoadingModule instances are created!

Motivation

It's a lot of work to manually set and retrieve loading state all over an application, so I wanted to make this easier. I preferred having more of a declarative approach, so I decided I'd design it in such a way that you can just say "give me the loading state for this action" and have the library figure out the rest.

Because almost any web application needs to track loading state for improving the user experience, I decided to make an NPM package so I can easily reuse the system in other React + Redux applications.

Tips

If you're calling the same end-points from multiple places in your application, export the associated loading module so you can easily re-use it.

TypeScript

This package supports TypeScript. It was written in TypeScript and its types come bundled with it.

License

MIT