1.0.7 • Published 6 years ago

redux-resx v1.0.7

Weekly downloads
7
License
MIT
Repository
github
Last release
6 years ago

Redux API Resource creator (redux-resx)

Build Status

Yet another Redux action creators, a reducer and middleware for resource-based APIs.

resx = shortening for resource

Based on the async actions pattern in redux (https://redux.js.org/docs/advanced/AsyncActions.html)

Installation

npm install --save redux-resx

Usage

Resource Definition

A resource is a grouping of redux reducers, actions and selectors for your api endpoint. You define a unique name for it and the url. You can also add your own reducer to augment the state at the "mountpoint" on the state store.

// somewhere like src/resources/user.js
import createResource from 'redux-resx';

export default createResource({
  // Required:
  name: '@resx/USER', // Unique namespace for actions and reducer
  url: '/users',

  // Optional (defaults shown)
  // This function should return the root object of where you mount your state
  baseSelector: s => s.resources,
  // Use this to add extra reducers which receive the state after the built-in reducer
  // has done it's thing - this can perhaps be used in conjunction with custom middleware
  // It only receives resource actions, not every action
  reducer: (state, _action) => state,
});


// src/resources/index.js
export default as user from './user';

Reducer

import { combineReducers } from 'redux';

import * as resources from '../resources';
import { reducer as resourceReducer } from 'redux-resx';

export default combineReducers({
  resources: combineReducers(resourceReducer(resources)),
});

Lets break this down a bit:

export default combineReducers({
  // 'resources' can be anywhere, you just need to specify a base selector that selects it in
  // create resource
  resources: combineReducers(
    // resourceReducer is really just transforms the object to something that combineReducers can use
    // Give it { users: [result of createResource] } and it will return { users: reducerFn } - simple
    resourceReducer(resources)
  ),
});


// Another way you could do this

import userResource from '../resources/users';
...
resources: combineReducers({
  myUser: userResource.reducer,
  //etc
})

Component

Please see the NB comments

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';

import { user as userResx } from '../resources';

// NB: New in 1.0.0+
// *************************************************************************
// You need to provide a namespace for your 'instance' (any string) that you want to use.
// This is so you can call a resource in multiple components without interferance.
const myUserResx = userResx.create('@HOME');
// If you omit the namespace, a default one will be used (essentially the same behaviour prior to 1.0.0)
// const myUserResx = userResx.create();
// Using the default method, returns a singleton instance for reuse
// const myUserResx = userResx.default();

const Home = React.createClass({
//....

  componentWillMount() {
    const { getUser, findUsers, resetUsers } = this.props;
    // XXX: Optional, you'll get old results before new ones are loaded if you don't do this.
    // New in 1.0.0: If your resource is only used in this component and you destroy on unmount,
    // you definitely/obviously won't need to use reset.
    resetUsers();
    findUsers({ status: 'active' }); // params of request
    getUser(123).then(...); // id=123 NB: only if middleware returns a promise
  },

  // NB: New in 1.0.0 - will remove the namespaced data entirely
  componentWillUnmount() {
    this.props.destroyResx();
  }

  render() {
    const { users, user } = this.props;

    return (
      <div>
        {users ? JSON.stringify(users) : null}
        {user ? JSON.stringify(user) : null}
      </div>
    );
  },
});

function mapStateToProps(state) {
  // Select the resource state
  const {
    hasLoaded, // true when find has been loaded before
    isBusy, // true when any of the following are true
    isFinding,
    isGetting,
    isCreating,
    isUpdating,
    isPatching,
    isRemoving,

    // Result for find - always an array (initial value: [])
    items,

    // Last result for create, get, update, patch, remove
    entity, // (initial value: undefined)
  } = userResx.selector(state);

  return {
    users: items,
    user: entity,
    isBusy,
  };
}

const { find: findUsers, get: getUser, reset: resetUsers, destroy: destroyResx } = myUserResx.actions;

export default connect(mapStateToProps, {
  findUsers,
  getUser,
  resetUsers,
  destroyResx,
})(Home);

Selector

Each resx has a selector function which can be used to select the resource from the state store.

A resx has the following structure:

// Initial structure of resx
{
  hasLoaded: false, // Has the resource loaded before (has find returned a result and items populated)
  isBusy: false, // true if any operation is running on this resx, otherwise false
  isFinding: false, // true if find call is busy, otherwise false
  isGetting: false, // true if get call is busy, otherwise false
  isCreating: false, // true if create call is busy, otherwise false
  isUpdating: false, // true if update call is busy, otherwise false
  isPatching: false, // true if patch call is busy, otherwise false
  isRemoving: false, // true if remove call is busy, otherwise false
  items: [], // The result of a find call
  entity: undefined, // The result of the last get, create, patch, update and remove call
  lastError: undefined, // The result of the call if it was an error
}

Middlewares

The middleware's job is to "handle" the actions coming in from resource action creators. This is where the side-effects are. A middleware is included which calls endpoints like you would expect, but you can implement your own or use e.g. sagas.

Builtin middleware

Add middleware to store in the normal way

// NB: Only bundled if you are using it
import middleware from 'redux-resx/middleware';
import fetch from 'isomorphic-fetch';
//... other imports

const resxMiddleware = middleware({
  baseUrl: '/api',
  provider: fetch, // could write adapter to convert fetch params to e.g. request/jquery
});

export default function createApplicationStore() {
  return createStore(
    reducers,
    compose(applyMiddleware(resxMiddleware))
  );
}

Other middleware

TODO

  • Example

Future Ideas

  • Middleware (separate package) that implements a decoupled cache using state
1.0.7

6 years ago

1.0.6

6 years ago

1.0.5

7 years ago

1.0.4

7 years ago

1.0.3

7 years ago

1.0.2

7 years ago

1.0.1

7 years ago

1.0.0

7 years ago

1.0.0-rc1

7 years ago

0.2.1

7 years ago

0.2.0

7 years ago

0.2.0-rc.1

7 years ago

0.1.0

7 years ago

0.0.4

7 years ago

0.0.3

7 years ago

0.0.2

7 years ago

0.0.1

7 years ago