Skip to content

Latest commit

 

History

History
745 lines (688 loc) · 20.1 KB

DOCS.md

File metadata and controls

745 lines (688 loc) · 20.1 KB

Documentation

Initialization redux-api endpoint

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

reduxApi(options, baseConfig)

  • 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, getState) {  //it's default value
          return {};
        }
      }
    }

Param baseConfig - additional base configuration Param baseConfig.prefix - custom prefix for ACTIONS if you use more then 1 restApi instance Type: String Default: ""

Configuration options

url

  • Description: url endpoint
  • Type: String
  • Example:
{
  entry: {
    url: "/api/v1/entry"
  }
}

urlOptions

  • Description: options for transforming urls
  • Type: Object
  • Example: Keys delimiter and arrayFormat are passed on to qs#parse and qs#stringify:
{
  entry: {
    url: "/api/v1/entry",
    urlOptions: {
      delimiter: ";",
      arrayFormat: "brackets"
    }
  }
}

To pass different options to #parse and #stringify, use the qsParseOptions and qsStringifyOptions keys:

{
  entry: {
    url: "/api/v1/entry?a[]=5,a[]=6",
    urlOptions: {
      arrayFormat: "brackets",
      qsParseOptions: {
        delimiter: /[,;]/
      },
      qsStringifyOptions: {
        delimiter: ";"
      }
    }
  }
}

This would re-encode the url to /api/v1/entry?a[]=5;a[]=6.

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, prevData, action) {
    data || (data = {});
    return { title: (data.title || ""), message: (data.message || "")};
  }

options

  • Description: options for rest-api backend. function(url, options)
  • Type: Object | Functions
  • 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"
          }
        };
      }

cache

  • Description: cache response. By default cache is turn off. If cache = true - this means that cache is permanent. Also cache can be object. see example
  • Type Boolean, Object, null
  • Default: null
  • Example:
{
  permanent: {
    url: "/api/v1/permanent",
    cache: true
  },
  expires1: {
    url: "/api/v1/expires/1",
    cache: { expire: 360 }, // 360 seconds
  },
  expires2: {
    url: "/api/v1/expires/2",
    cache: {
      expire: new Date("...."), // use concrete Date
      id(params, params) {
        // here you can overwrite cache id for request
        return `you custom id for request`;
      }
    }
  }
}

broadcast

  • @deprecated
  • 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;
  }
}

reducer

  • Description: Define your custom reducer to catch other events and modify state of current entry ATTENTION: custom reducer can't catch default events for current entry.
  • Type: Function
  • Default: null
  • Example:
const rest = reduxApi({
  hello: "/api/hello",
  item: {
    url: "/api/item",
    reducer(state, action) {
      /*
      ATTENTION: this.events.item.actionSuccess and other default redux-api events never catch there
      */
      // context has instance
      if (action.type === "MY_CUSTOM_EVENT") {
        return { ...state, value: action.value };
      } else if (action.type === this.events.hello.actionSuccess) {
        return { ...state, value: action.value };
      } else {
        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
  • Example: It usefull, for example, when you need to manipulate list of items. But you don't want to persist information about each manipulation, you want to save it in list.
const rest = reduxApi({
  items: "/api/items",
  item: {
    url: "/api/item/:id",
    virtual: true, //reducer in this case doesn't generate
    postfetch: [
      function({ dispatch, actions }) {
        dispatch(actions.items()); // update list of items after modify any item
      }
    ]
  }
});

In this case you global state is look like this:

{ items: [ ... ] }

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 {profile: {data: {uuid}}} = getState();
      return { ...params, body: { ...params.body, uuid }};
    }
  },
  friends: {
    url: "/user/:name/friends",
    prefetch: [
      function({actions, dispatch, getState, requestOptions}, cb) {
        const {profile: {data: {uuid}}} = getState();
        const {pathvars: {name}} = requestOptions;
        uuid ? cb() : dispatch(actions.profile({name}, cb));
      }
    ],
    options: function(url, params, getState) {
      const {profile: {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, request}) {
        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();

crud

  • Description: autogenerate helpers ("get", "post", "put", "delete", "patch") for selected endpoint. Also you can overwrite autogenerate action with helpers definitions.
  • Type: Boolean
  • Default: false
  • Example:
{
  test: {
    url: "/test/:id",
    crud: true
  }
}

//using
rest.actions.test.get({ id: 1})
rest.actions.test.post({ id: 1}, { body: "data" }, (err, data)=> {
  //code
});
rest.actions.test.put({ id: 1}, { body: "data" })
rest.actions.test.delete({ id: 1 });

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 current example we use adaptersFetch adapter for rest backend using fetch API for rest isomorphic-fetch
  • Example:
import adapterFetch from "redux-api/lib/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 client-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 client-side use default rootUrl, and for backend use http://localhost:80 for example. For client-side for request /api/get will be /api/get and for backend will be http://localhost:80/api/get
  • Type: String | Functions
  • Example:
const rest = reduxApi({...});
rest.use("rootUrl", "http://localhost:3000");

Or a function

const rest = reduxApi({...});
rest.use("rootUrl", function(url, params, getState) {
  return getState().config.rootUrl;
});

options

  • Description: Apply add options for each rest call.
  • Type: String | Functions
  • Example:
const rest = reduxApi({...});
rest.use("options", function() {
  const headers = {
    'User-Agent': 'foodsoft-shop', // @todo add version
    'Accept': 'application/json'
  };
  return { headers: headers };
});

Or a function

const rest = reduxApi({...});
rest.use("options", function(url, params getState) {
  return {
    headers: {
      'X-Token': getState().user.accessToken
    }
  };
});

middlewareParser

  • Description: if you use middleware different from redux-thunk you can realize custom behaviour for argument parser.
  • Example:
// Custom middleware
const cutsomThunkMiddleware = ({ dispatch, getState }) => next => action => {
  if (typeof action === 'function') {
    return action({ dispatch, getState });
  }
  return next(action);
};

// middlewareParser
reduxApi({ ... }).use("middlewareParser",
  ({ dispatch, getState })=> {
    return { getState, dispatch };
  });

responseHandler

  • Description: catch all http response from each redux-api endpoint. First argument is Error is response fail, second argument data from success response. It can be used for logging, error handling or data transformation.
  • Example:
reduxApi({ ... }).use("responseHandler",
  (err, data)=>
    err ? console.log("ERROR", err) : console.log("SUCCESS", data));
reduxApi({ ... }).use("responseHandler",
  (err, data)=> {
    if (err.message === 'Not allowed') {
      throw new NotAllowedError();
    } else {
      return data;
    }
  });

init(adapter, isServer, rootUrl)

  • @deprecated
  • Description: reduxApi initializer returns not initialized object. You need to call init for initialize it.
  • Type: Function
  • Param adapter - backend adapter. In current 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 client-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 client-side use default rootUrl, and for backend use http://localhost:80 for example. For client-side 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/lib/adapters/fetch";
const rest = reduxApi({
  ... //config
});
rest.init(adapterFetch(fetch), false, "http://localhost:3000");

actions

  • Description: 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 request 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));
}

abort()

  • Description: abort loading request
  • Type: null
  • Example:
import {actions} from "./rest";
dispatch(actions.entries({ id: 1 }))
actions.entries.abort() // abort previous request
dispatch(actions.entries({ id: 2 }))

force(urlparams, params, callback)

  • Description: abort previous request if it performs and after that perform new request. This method combines abort and direct call action methods.
  • Type: Function
  • Example:
import {actions} from "./rest";
dispatch(actions.entries({ id: 1 }))
dispatch(actions.entries.force({ id: 2 }))

reset(mutation)

  • Description: Reset state of current reducer and application abort request if it processed.
  • Type: Function
  • Param mutation: if mutation equal sync, it reset only sync flag in store.
  • Example:
import {actions} from "./rest";
function onLeave(state, replaceState, cb) {
  dispatch(actions.entries.reset(cb));
}

request()

  • Description: Pure xhr request is 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

Events

Each endpoint in redux-api infrastructure has own collection of methods.

  • actionFetch - emits when endpoint's call is started
  • actionSuccess - emits when endpoint's call finishes with success result
  • actionFail - emits when endpoint's call finishes with error result
  • actionReset - emits when reset action was called

you can get access for anyone using next accessible properties

rest.events.user.actionFetch // actionSuccess actionFail actionReset
....

It's very useful when you need to update external reducer using information from redux-api.

const initialState = { access: false };
function accessReducer(state=initialState, action) {
  switch (action.type) {
    case UPDATE_ACCESS:  // manual update
      return { ...state, access: action.access };
    case rest.events.user.actionSuccess: // user has own information about access
      return { ...state, access: action.data.access };
    default:
      state;
  }
}

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));

Store state schema

import reduxApi from "redux-api";

const rest = reduxApi({
  user: "/user/1"
});

In the above example, an endpoint for a user object is created. The corresponding initial store state for this object is the following:

// 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
  }
}
  • The sync flag will be set to true after the first dispatched request is handled.
  • The syncing flag is set to true while a dispatched sync request is being handled, but only when the sync flag is false. This can only happen once when not manually resetting the sync flag during execution, since the sync flag is set to true after the first dispatched request is handled.
  • The loading flag is set to true while a dispatched request is being handled. After the dispatched request is handled, its value will be reset to false.
  • The error property contains the response error of a dispatched request after it is handled.
  • The data property contains the response data of a dispatched request after it is handled.