nuǝW pǝuoᴉʇᴉsoԀ ʎlǝʌᴉʇɐƃǝN
    Wondering what’s next for npm?Check out our public roadmap! »

    redux-entity
    TypeScript icon, indicating that this package has built-in type declarations

    8.0.20 • Public • Published
    logo

    At its core, redux-entity is just a reducer that utilizes a specialized thunk, which is designed to handle asynchronous actions in the form of a Promise.

    Most web applications need to handle a variety of domain entities such as orders, products, users, etc. This library was designed to manage these objects within Redux in a predictable and scalable way.

    Playground

    Demos

    Check out the demo repository at https://github.com/mikechabot/react-boilerplate

    Install

    $ npm install redux-entity

    $ yarn add redux-entity

    Getting Started

    The API is very simplistic; a thunk called GetEntity is exposed, which does all the heavy lifting.

    Every entity you fetch is automatically associated with the following properties to ensure predictability. No need to track these yourself.

    interface EntityState {
      /** Data returned from the resolved promise */
      data?: any;
     
      /** Error returned from the rejected promise */
      error?: Error;
     
      /** Whether the entity promise is pending */
      isFetching: boolean;
     
      /** Timestamp of the promise's last resolution or rejection */
      lastUpdated?: Date;
    }

    Integrate into Redux

    To get started, import the reducer from redux-entity, and combine with your existing reducers.

    By default, we're carving out a space in the Redux tree with the key of entities, but you can rename it to whatever you'd like.

    // root-reducer.ts
    import { reducer as entities } from 'redux-entity';
    import { combineReducers } from 'redux';
     
    export default combineReducers({
        ...<existing reducers>,
        entities
    });

    Now we're ready to use GetEntity.

    GetEntity(key, promise, options)

    When using GetEntity, you only need to provide two elements: a key to uniquely identify the entity, and a promise to fetch the data.

    import { GetEntity } from 'redux-entity';
    import OrderService from './services/order-service';
     
    const key = 'orders';
    const promise = OrderService.getOrders();
     
    export const loadOrders = () => GetEntity(key, promise);

    Redux Store

    Let's take a look at what the Redux store looks like when loadOrders is invoked.

    In the context of React, let's say we have an <Orders /> component; when the component mounts, we'll want to fetch our data. See Detailed Usage for the full React component.

    react

    While loadOrders is pending, isFetching is set to true:

    fetching

    If loadOrders succeeds, the results are stamped on the store at entities.orders.data, and lastUpdated is set:

    succeed

    If loadOrders fails, the results are stamped on the store at entities.orders.error, and lastUpdated is set:

    fail

    If we need to load more entities, we just create additional thunks with GetEntity, and invoke them as described above.

    Every entity we fetch will be stamped on the entities tree.

    state

    Detailed Usage

    The guide below assumes you've already injected the Redux store into your React application.

    1. Configure the root reducer

    Follow along with Integrate into Redux to integrate the reducer into your existing Redux store.

    2. Create a custom thunk

    Create a thunk using GetEntity. You only need to provide a key that uniquely identifies the entity, and a data promise.

    You can optionally pass a configuration to GetEntity. See Configuration:

    import { GetEntity } from 'redux-entity';
    import OrderService from './services/order-service';
     
    const entityKey = 'orders';
    const promise = OrderService.getOrders();
     
    export const loadOrders = () => GetEntity(key, promise);

    3. Create a React component

    Here's a full React component that utilizes our loadOrders example. At this point, loadOrders is no different than any other Redux thunk.

    Check out the CodeSandbox

    import React, { useEffect } from 'react';
    import { useSelector, useDispatch } from 'react-redux';
     
    import { loadOrders } from './utils';
     
    import Buttons from './Buttons';
    import State from './State';
     
    export default function Orders() {
      const dispatch = useDispatch();
     
      useEffect(() => {
        dispatch(loadOrders());
      }, [dispatch]);
     
      const { orders } = useSelector((state) => state.entities);
     
      let body, isFetching;
     
      if (orders) {
        isFetching = orders.isFetching;
        const { data, error } = orders;
        if (isFetching) {
          body = 'Fetching Orders...';
        } else if (error) {
          body = error.message;
        } else if (data) {
          body = `Found ${orders.data.length} Orders!`;
        }
      }
     
      return (
        <div>
          {body}
          <br />
          <Buttons disabled={isFetching} />
          <State />
        </div>
      );
    }

    Configuration Options

    Optionally pass a configuration with any of the following properties:

    Argument Default Description
    silent false If true, don't toggle isFetching when the thunk is invoked
    append false If true, append the results of each invocation to the existing data property instead of overwriting it
    processors undefined Hook into the GetEntity lifecycle. Each processor has access to Redux's dispatch and getState along with either the data or error object of the entity. See Processors

    The options configuration must adhere to the following interface:

    interface ReduxEntityOptions {
      [OptionKey.Silent]?: boolean;
      [OptionKey.Append]?: boolean;
      [OptionKey.Processors]?: Processors;
    }
     
    enum OptionKey {
      Silent = 'silent',
      Append = 'append',
      Processors = 'processors',
    }

    Example Configurations

    Simple configuration:

    const key = 'orders';
    const promise = OrderService.getOrders();
    const options = { silent: true, append: true };
     
    export const loadOrders = () => GetEntity(key, promise, options);

    Dynamically pass a configuration:

    const key = 'orders';
    const promise = OrderService.getOrders();
     
    export const loadOrders = (options) => GetEntity(key, promise, options);

    Processors

    Processors are optional and in most cases won't be needed, however you can take additional action when an entity's promise either resolves or rejects by hooking into the processors below.

    Processor When is this executed?
    beforeSuccess After promise resolution, but before data is dispatched to the store. Must return any
    afterSuccess After promise resolution, and after the store has been updated
    beforeFailure After promise rejection, but before the error is dispatched to the store. Must return error
    afterFailure After promise rejection, and after the store has been updated

    The processor object must adhere to the following interface:

    type Processors = {
      [key in ProcessorType]?: (
        dataany,
        dispatchThunkDispatch<ReduxEntityState, unknown, AnyAction>,
        getStateGetState
      ) => any | void;
    };
     
    enum ProcessorType {
      BeforeSuccess = 'beforeSuccess',
      AfterSuccess = 'afterSuccess',
      BeforeFailure = 'beforeFailure',
      AfterFailure = 'afterFailure',
    }

    Configuration with processors:

    const key = 'orders';
    const promise = OrderService.getOrders();
     
    const options = {
      silent: true,
      processors: {
        beforeSuccess: (data, dispatch, getState) => {
          // Do synchronous stuff
          // *Must* return data to be dispatched to the store
          return Object.keys(data);
        },
        beforeFailure: (error, dispatch, getState) => {
          // Do synchronous stuff
          // *Must* return an error to the dispatched to the store
          return new Error('Intercepted error!');
        },
      },
    };
     
    export const loadOrders = () => GetEntity(key, promise, options);

    Additional Thunks

    The following actions can be use to reset or delete your entity.

    Check out the Demos to see these in action.

    Action creator Description
    ResetEntity Reset the entity to the original EntityState, and set lastUpdated
    DeleteEntity Delete the entity from state

    Example usage

    Check out the CodeSandbox

    import React, { useEffect } from 'react';
    import { useSelector, useDispatch } from 'react-redux';
     
    import Buttons from './Buttons';
    import State from './State';
     
    import { loadOrders } from './utils';
     
    export default function App() {
      const { orders } = useSelector((state) => state.entities);
     
      const dispatch = useDispatch();
      useEffect(() => {
        dispatch(loadOrders());
      }, [dispatch]);
     
      let body, isFetching;
     
      if (orders) {
        isFetching = orders.isFetching;
        const { data, error } = orders;
        if (isFetching) {
          body = <em>Fetching Orders...</em>;
        } else if (error) {
          body = <span className="error">{error.message}</span>;
        } else if (data) {
          body = `Found ${orders.data.length} Orders!`;
        } else {
          body = 'No Data!';
        }
      } else {
        body = 'No Entity!';
      }
     
      return (
        <div className="app">
          <h3>Playground</h3>
          <div className="body">{body}</div>
          <Buttons disabled={isFetching} />
          <State />
        </div>
      );
    }

    Install

    npm i redux-entity

    DownloadsWeekly Downloads

    129

    Version

    8.0.20

    License

    MIT

    Unpacked Size

    42.3 kB

    Total Files

    18

    Last publish

    Collaborators

    • avatar