Redux reducer, actions, and serializers for JSON:API


License
MIT
Install
npm install redux-jsonapi@1.1.3

Documentation

Redux JSON:API

Travis CI Code Climate Test Coverage

Redux JSON:API is a collection of actions and reducers for Redux that help ease the use of a JSON:API compliant API.

JSON:API is amazing for data transport, but once a client has the data it can be a little frustrating to work with. Redux JSON:API takes the guess-work and bike-shedding about how to structure, store, and use your JSON:API data in a Redux application.

Instalation

To install the stable version:

npm install --save redux-jsonapi

Usage

The Basics

The following will fetch a widget from GET http://example.com/widgets/1 (including it's associated foobars) add it to the application state under api (indexed by it's ID), and output the new state to the console.

import { createStore, combineReducers } from 'redux';
import { apiReducer, apiActions, createApiMiddleware } from 'redux-jsonapi';

const reducers = combineReducers({
  api: apiReducer
})

const apiMiddleware = createApiMiddleware('http://example.com');

const store = createStore(reducers, applyMiddleware(apiMiddleware);

store.subscribe(() => {
  console.log(store.getState().api);
});

store.dispatch(apiActions.read({ id: 1, _type: 'widgets' }, {
  params: {
    include: 'foobars'
  },
}));

Creating a widget via the API and adding it to the application state on success isn't much more complicated:

store.dispatch(apiActions.write({ _type: 'widgets', name: 'Super Cool Widget' }));

Updating an existing record is nearly identical - simply make sure the resource has an ID property:

store.dispatch(apiActions.write({ _type: 'widgets', id: 1, name: 'Super Cool Widget With A New Name' }));

Deleting a record is very similar:

store.dispatch(apiActions.remove({ _type: 'widgets', id: 1 }));

Nested Resources

To access the entities on a nested URL, provide an array of parent resources to the action. The following will fetch the entities from GET http://example.com/widgets/1/doodads:

store.dispatch(apiActions.read([{ _type: 'widgets', id: 1 }, { _type: 'doodads' }]));

Note: the order of resources in the array determines the order of nesting.

Custom Headers

You can extend the default headers by simply passing an object as 2nd parameter into createApiMiddleware.

const apiMiddleware = createApiMiddleware('http://example.com', {
    Authorization:  'JWT Token'
});

With each apiActions method you can provide additional headers as well

store.dispatch(apiActions.read({ id: 1, _type: 'widgets' }, {
  params: {
    include: 'foobars'
  },
  headers: {
    cookies: 'MyCookie'
  }
}));

Using Data

Data received from the JSON:API store is normalized into categories by resource type, and indexed by the resource's ID. The structure looks something like the following:

{
  api: {
    widgets: {
      1: {
        id: 1,
        type: 'widgets',
        attributes: {
          name: 'Super Cool Widget',
        },
        relationships: {
          doodad: {
            data: {
              id: 1,
              type: 'doodads',
            }
          }
        }
      },
      2: { ... },
    },
    doodads: {
      1: {
        id: 1,
        type: 'doodads',
        attributes: {
          name: 'Nifty Doodad',
        },
      },
      2: { ... },
    }
  }
}

Since often the data that we receive from the API contains associations to other resources, this isn't a very easy structure to work with out of the box. Redux JSON:API contains utilities that make serializing and deserializing resources between the JSON:API structure and a vanilla Javascript object structure much easier.

The following will fetch a widget from the API at /widgets/1, and upon being loaded into the Redux state it will log a deserialized instance of it to the console.

import { apiActions, deserialize } from 'redux-jsonapi';

store.subscribe(() => {
  const { api } = store.getState();
  const widget = deserialize(api.widgets[1], api);
  console.log(widget);
});

store.dispatch(apiActions.read({ id: 1, _type: 'widgets' }));

The denormalized instance looks something like this:

const widget = {
  _type: 'widgets',
  id: 1,
  name: 'Super Cool Widget',
  doodad: function() { // Calling this returns the associated doodad }
}

With this new denormalized resource, we can access the widget's name via widget.name, and it's associated doodad by calling widget.doodad().

When it comes time to update the widget, simply change it's values and dispatch the apiActions.write action with the object.

widget.name = 'New Name';
widget.doodad = () => newDoodad;
store.dispatch(apiActions.write(widget));

Example

See the example directory.

License

MIT