andrefgneves / redux-api

Flux REST API for redux infrastructure

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Redux-api

Flux REST API for redux infrastructure

Build Status NPM version Coverage Status

Inspired by Redux-rest and is recommended to work with Redux.

Install

with npm

npm install redux-api --save

with bower

bower install redux-api --save

=======

Examples

examples/isomorphic - React + Redux + React-Router + Redux-api with webpack and express + github api

Documentation

###Initialization redux-api endpoint

import reduxApi, {transformers} from "redux-api";

reduxApi(options)

  • @description create endpoint
  • @param options - configuration of rest-api endpoints
    • @type: Object
    • @default: {}
    • @example:
      Simple endpoint definition GET /api/v1/entry where response is Object
    {
      entry: "/api/v1/entry",
    }
    // equivalent
    {
      entry: {
        url: "/api/v1/entry"
      }
    }
    // equivalent
    {
      entry: {
        url: "/api/v1/entry",
        transformer: transformers.object, //it's default value
        options: {}                       //it's default value
      }
    }
    // equivalent
    {
      entry: {
        url: "/api/v1/entry",
        transformer: transformers.object, //it's default value
        options: function(url, params) {  //it's default value
          return {};
        }                       
      }
    }

###Configuration options

url

  • @description: url endpoint
  • @type: String
  • @example:
{
  entry: {
    url: "/api/v1/entry"
  }
}

transformer

  • @description: function for rest response transformation
  • @type: Function
  • @default: transformers.object
  • @example: It's a good idea to write custom transformer
    for example you have response
  { "title": "Hello", "message": "World" }
Custom transformer
  function customTransformer(data) {
    data || (data = {});
    return { title: (data.title || ""), message: (data.message || "")};
  }

options

  • @description: options for rest-api backend. function(url, options)
  • @type: Object | Funtions
  • @default: null
  • @example: if you use isomorphic-fetch backend
      options: {
        method: "post",
        headers: {
          "Accept": "application/json",
          "Content-Type": "application/json"
        }
      }
      // equivalent
      options: function() {
        return {
          method: "post",
          headers: {
            "Accept": "application/json",
            "Content-Type": "application/json"
          }
        };
      }

####broadcast

  • @description: list of actions which would emit after data fetching.
  • @type: Array
  • @default: null
  • @example:
import {ACTION_ENTRY_UPDATE} from "./constants";
....
entry: {
  url: "/api/v1/entry",
  broadcast: [ ACTION_ENTRY_UPDATE ]
}
// in your redux reducer
function (state, action) {
  switch (action.type) {
  case ACTION_ENTRY_UPDATE:
    return {
      ...state,
      data: action.data // fetching data
    };
  default:
    return state;
  }
}

####virtual

  • @description: if virtual is true this endpoint doesn't create reducer and doesn't emit redux-api actions. All data broadcasting by actions from broadcast list.
  • @type: Array
  • @default: false

####prefetch

  • @description: you can organize chain of calling events before the current endpoint will be executed
  • @type: Array
  • @default: null
  • @example:
{
  user: "/user/info",
  profile: "/user/:name",
  changeName: {
    url: "/user/changename",
    prefetch: [
      function({actions, dispatch, getState}, cb) {
        const {user: {data: {name}}} = getState();
        name ? cb() : dispatch(actions.user(cb));
      }, 
      function({actions, dispatch, getState}, cb) {
        const {user: {data: {name}}, profile: {data: {uuid}}} = getState();
        uuid ? cb() : dispatch(actions.profile({name}, cb));
      }
    ],
    options: function(url, params, getState) {      
      const {user: {data: {uuid}}} = getState();
      return { ...params, body: { ...params.body, uuid }};
    }
  }
}

####postfetch

  • @description: you can organize chain of calling events after the current endpoint will be successful executed
  • @type: Array
  • @default: null
  • @example:
{
  user: "/user/info",
  logout: {
    url: "/user/logout",
    postfetch: [
      function({data, actions, dispatch, getState}) {
        dispatch(actions.user.reset());
      }      
    ]    
  }
}

####validation (data, callback)

  • @param data - response data

    type: Object

  • @param callback - you need to execute this callback function to finish data validation

    type: Function

  • @example

{
  test: {
    url: "/api/test",
    validation: (data, cb) {
      // check data format
      let error;
      if (data instanceOf Array) {
        error = "Data must be array";
      }
      cb(error);
    }
  }
}

reducerName

  • @description: Sometimes though, you might want named actions that go back to the same reducer. For example:
  • @type: String
  • @example:
import reduxApi, {transformers} from "redux-api";
const rest = reduxApi({
  getUser: {
    reducerName: "user"
    url: "/user/1", // return a user object
  }
  updateUser: {
    reducerName: "user"
    url: "/user/1/update",
    options: {
      method: "post"
    }
  }
});
const {actions} = rest;

// In component with redux support (see example section)
const {dispatch} = this.props;
dispatch(rest.actions.getUser()); // GET "/api/v1/entry"
dispatch(rest.actions.updateUser({}, {
  body: JSON.stringify({ name: "Hubot", login: "hubot"})
}));  // POST "/api/v1/entry/1" with body

In the above example, both getUser, and updateUser update the same user reducer as they share the same reducerName For example used es7 javascript

####helpers

  • @description: you can create custom helper function which work with this rest endpoint but with different parameters.
  • @type: Object
  • @example:
{  
  logger: "/api/logger",
  test: {
    url: "/api/test/:name/:id",
    helpers: {
      get(id, name) {
        return [{id, name}], {}]
      },
      post(id, name, data) {
        const {uuid} = this.getState().test;
        const urlparams = {id, name};
        const params = {body: {uuid, data}};
        return [urlparams, params];
      },
      // complicated async logic
      async() {
        const {dispatch} = this;
        return (cb)=> {
          dispatch(rest.actions.logger((err)=> {
            const args = [{id: 1, name: "admin"}];
            cb(err, args);
          }));
        };
      }
    }
  }
}
// using helpers
rest.actions.test.get(1, "admin");
// with callback
rest.actions.test.post(1, "admin", {msg: "Hello"}, (err)=> {
// end of action
});
rest.actions.test.async();

reduxApi object

####use(key, value)

  • @description initialize reduxApi with custom properties
  • @param key - name of property
  • @param value - value of property

####list of properties ####fetch

  • @description backend adapter. In curent example we use adaptersFetch adapter for rest backend using fetch API for rest isomorphic-fetch
  • @example
import adapterFetch from "redux-api/adapters/fetch";
const rest = reduxApi({...});
rest.use("fetch", adapterFetch(fetch));

####server

  • @description - redux api is isomorphic compatible see examples/isomorphic By default server===false for clien-size mode. If server===true redux-api works in server-size mode.
  • @default false
const rest = reduxApi({...});
rest.use("server", true); 

####rootUrl

  • @description - root url for every endpoint. very usefull for isomorphic(universal) app. For clientsize use default rootUrl, and for backend use http://localhost:80 for example. For cliendsize for request /api/get will be /api/get and for backend will be http://localhost:80/api/get
  • @example
const rest = reduxApi({...});
rest.use("rootUrl", "http://localhost:3000");

####init(adapter, isServer, rootUrl)

  • @deprecated
  • @description: reduxApi initializer returns non initialized object. You need to call init for initilize it.
  • @type: Function
  • @param adapter - backend adapter. In curent example we use adaptersFetch adapter for rest backend using fetch API for rest isomorphic-fetch
  • @param isServer - redux api is isomorphic compatible see examples/isomorphic By default isServer===false for clien-size mode. If isServer===true redux-api works in server-size mode.
  • @param rootUrl - root url for every endpoint. very usefull for isomorphic(universal) app. For clientsize use default rootUrl, and for backend use http://localhost:80 for example. For cliendsize for request /api/get will be /api/get and for backend will be http://localhost:80/api/get.
  • @example:
import "isomorphic-fetch";
import reduxApi from "redux-api";
import adapterFetch from "redux-api/adapters/fetch";
const rest = reduxApi({
  ... //config
});
rest.init(adapterFetch(fetch), false, "http://localhost:3000");

actions

  • @descritpion: list of redux actions for rest manipulations
  • @type: Object
  • @example:
const rest = reduxApi({
  entries: "/api/v1/entry",
  entry: {
    url: "/api/v1/entry/:id",
    options: {
      method: "post"
    }
  }
});
// ....
const {actions} = rest;
/*
initialState for store
store = {
  entries: {
    loading: false, // request finish flag
    sync: false,    // data has loaded minimum once
    data: {}        // data
  },
  entry: { loading: false, sync: false, data: {} },
}
*/

// In component with redux support (see example section)
const {dispatch} = this.props;
dispatch(rest.actions.entries()); // GET "/api/v1/entry"
dispatch(rest.actions.entry({id: 1}, {
  body: JSON.stringify({ name: "Hubot", login: "hubot"
}}));  // POST "/api/v1/entry/1" with body
dispatch(rest.actions.entries.reset()); 
dispatch(rest.actions.entries.sync());

###Actions sub methods

sync(urlparams, params, callback)

  • @description: this method save you from twice requests flag sync. if sync===true requst wouldn't execute. In server-side mode calls twice
  • @param urlparams - update url according Url schema
  • @param params - add additional params to rest request
  • @param callback - callback function when action ends
  • @type: Function
  • @example:
import {actions} from "./rest";
function onEnter(state, replaceState, callback) {
  dispatch(rest.actions.entries.sync(callback));  
}

reset()

  • @description: Reset state of current reducer
  • @type: Function
  • @example:
import {actions} from "./rest";
function onLeave(state, replaceState, callback) {
  dispatch(rest.actions.entries.sync(callback));  
}

request()

  • @description: Pure xhr request without sending events or catching reducers.
  • @type: Function
  • @example:
import {actions} from "./rest";
actions.entries.request().then((data)=> {
  ....
});

Url schema

/api/v1/user/:id

rest.actions.user({id: 1}) // /api/v1/user/1

/api/v1/user/(:id)

rest.actions.user({id: 1}) // /api/v1/user/1

/api/v1/user/(:id)

rest.actions.user({id: 1, test: 2}) // /api/v1/user/1?test=2

###Tools ####async

  • @description - helps to organize chain call of actions
  • @example
import reduxApi, { async } from "redux-api";
const rest = reduxApi({
  test: "/api/test",
  test2: "/api/test2",
  test3: "/api/test3"
});
async(dispatch, 
  (cb)=> rest.actions.test(cb),
  rest.actions.test2
).then((data)=> async(rest.actions.test3));

Full example Example

rest.js

import "isomorphic-fetch";
import reduxApi, {transformers} from "redux-api";
import adapterFetch from "redux-api/adapters/fetch";
export default reduxApi({
  // simple edpoint description
  entry: `/api/v1/entry/:id`,
  // complex endpoint description
  regions: {
    url: `/api/v1/regions`,
    // reimplement default `transformers.object`
    transformer: transformers.array,
    // base endpoint options `fetch(url, options)`
    options: {
      header: {
        "Accept": "application/json"
      }
    }
  }
}).init(adapterFetch(fetch)); // it's nessasary to point using rest backend

index.jsx

import React, {PropTypes} from "react";
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunk from "redux-thunk";
import { Provider, connect } from "react-redux";
import rest from "./rest"; //our redux-rest object

const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(rest.reducers);
const store = createStoreWithMiddleware(reducer);

@connect((state)=> ({ entry: state.entry, regions: state.regions }))
class Application {
  static propTypes = {
    entry: PropTypes.object.isRequired,
    regions: PropTypes.array.isRequired,
    dispatch: PropTypes.func.isRequired
  }
  componentDidMount() {
    const {dispatch} = this.props;
    // fetch `/api/v1/regions
    dispatch(rest.actions.regions.sync());
    //specify id for GET: /api/v1/entry/1
    dispatch(rest.actions.entry({id: 1}));
  }
  render() {
    const {entry, regions} = this.props;
    const Regions = regions.data.map((item)=> <p>{ item.name }</p>)
    return (
      <div>
        Loading regions: { regions.loading }
        <Regions/>
        Loading entry: {entry.loading}
        <div>{{ entry.data.text }}</div>
      </div>
    );
  }
}

React.render(
  <Provider store={store}>
    { ()=> <Application /> }
  </Provider>,
  document.getElementById("content")
);

Store state schema

const rest = reduxApi({
  user: "/user/1"
});
// initialState
{
  user: {
    sync: false,    // State was update once
    syncing: false, // State syncing is in progress
    loading: false, // State updating is in progress
    error: null,    // response error
    data: []        // response data
  }
}

Releases Changelog

About

Flux REST API for redux infrastructure


Languages

Language:JavaScript 100.0%