2.0.0 • Published 6 years ago

@gigster/module-redux-models v2.0.0

Weekly downloads
4
License
UNLICENSED
Repository
-
Last release
6 years ago

redux-models

RoleNameEmailSlack
Product OwnerFrances Haugenfrances@gigster.com@frances
MaintainerGeoff Kindergeoff.kinder@gigster.com@geoff
DeveloperAlice Wangalice@gigster.com@alicebot
DeveloperDan Isazadan@gigster.com@dan
DeveloperMark Miyashitamark.miyashita@gigster.com@mark

Overview

Adds a redux data layer based on the schema defined in the gig.yaml. The generated redux data layer includes:

  • Actions
  • Action Creators
  • API (data fetching)
  • Reducers
  • Selectors

Usage

Specification

NameStatusDescription
generateTeststrue/falseSet to true if you would like generated tests to be created for you for the redux layer.

Dependencies

This module depends on these frontend modules:

  • create-react-app block, located here
  • react-base module, located here
  • redux-request module, located here

This module also integrates with the loopback-models module, located here, for generation of RESTful endpoints for each model. If it is not used, then your API will need to provide these RESTful endpoints.

To run the generated tests, you must also use the react-jest module located here.

Example Scenario: Generated Code

Gig.yaml (schema definition)

Models section of gig.yaml

...
models:
  - name: user
    properties:
      - name: id
        type: number
        id: true
      - name: email
        type: string
      - name: password
        type: string
        hidden: true
        obscured: true
 ...

Generated Users Reducer

import createReducer from './createReducer';
import utils from './utils';

import {
  USERS_REQUEST,
  USERS_OK,
  USERS_ERROR,
  USERS_RESET,
} from '../actions/users';

const initialState = {
  loading: false,
  request: undefined,
  data: {},
  error: undefined,
};

const handlers = {
  [USERS_REQUEST]: (state, { payload }) => ({
    ...state,
    loading: true,
    request: payload,
  }),
  [USERS_OK]: (state, { payload }) => ({
    ...state,
    loading: false,
    data: {
      ...state.data,
      ...utils.arrayToMap(payload.response),
    },
  }),
  [USERS_ERROR]: (state, { payload }) => ({
    ...state,
    loading: false,
    error: payload.error,
  }),
  [USERS_RESET]: () => ({
    ...initialState,
  }),
};

export default createReducer(handlers, initialState);

Generated Users Actions (actions/users.js)

  • USERS_REQUEST: dispatched before a request to track params of the request and to denote a request is in flight
  • USERS_OK: dispatched when a request is successful to track the response and denote that a request is complete
  • USERS_ERROR: dispatched when a request has errored to track the error response
  • USERS_RESET: dispatched to reset data back to initial values
...
/* eslint-disable no-unused-vars */
export const USERS_REQUEST = 'USERS_REQUEST';
export const USERS_OK = 'USERS_OK';
export const USERS_ERROR = 'USERS_ERROR';
export const USERS_RESET = 'USERS_RESET';

const usersRequest = value => ({
  type: USERS_REQUEST,
  payload: value,
});

const usersOk = value => ({
  type: USERS_OK,
  payload: value,
});

const usersError = value => ({
  type: USERS_ERROR,
  payload: value,
});

const usersReset = value => ({
  type: USERS_RESET,
  payload: value,
});
/* eslint-enable */
...

Generated Users Action Creators (actions/users.js)

All action creators except for reset follow the pattern:

  • dispatch USERS_REQUEST initially to track the start of the request
  • dispatch USERS_OK upon a successful response
  • dispatch USERS_ERROR if the response contains an error

Reset action creators do not send a request and only dispatch USERS_RESET.

Generated Action Creators:

  • fetchUsers: fetches multiple users
  • fetchUser: fetches a single user, by id
  • createUser: creates a single user
  • updateUser: updates a single user
  • deleteUser: deletes a single user
  • resetUsers: resets the user data in global state to initial values
...
const fetchUsers = (args = {}) => (dispatch, getState) => {
  const state = getState();
  const token = getAuthToken(state);

  args.token = token;

  if (!args.include) {
    args.include = [];
  }

  const query = args.query || {};
  delete args.query;

  const include = Object.keys(query);
  args.include = args.include.concat(include);

  dispatch(usersRequest(args));

  return UsersApi.fetchUsers(args)
    .then((response) => {
      const requests = fetchNestedData({
        list: response,
        dispatch,
        token,
        query,
        actionCreatorMap,
      });

      const out = {
        request: args,
        response,
      };

      requests.push(dispatch(usersOk(out)));

      return Promise.all(requests);
    })
    .catch((error) => {
      const out = {
        request: args,
        error,
      };

      return dispatch(usersError(out));
    });
};

const fetchUser = (args = {}) => (dispatch) => {
  args.where = args.where || {};
  args.where = {
    ...args.where,
    id: {
      inq: [args.id],
    },
  };

  delete args.id;

  return dispatch(fetchUsers(args));
};

const createUser = (args = {}) => (dispatch, getState) => {
  const state = getState();
  const token = getAuthToken(state);

  args.token = token;

  dispatch(usersRequest(args));

  return UsersApi.createUser(args)
    .then((response) => {
      const out = {
        request: args,
        response,
      };

      return dispatch(usersOk(out));
    })
    .catch((error) => {
      const out = {
        request: args,
        error,
      };

      return dispatch(usersError(out));
    });
};

const updateUser = (args = {}) => (dispatch, getState) => {
  const state = getState();
  const token = getAuthToken(state);

  args.token = token;

  dispatch(usersRequest(args));

  return UsersApi.updateUser(args)
    .then((response) => {
      const out = {
        request: args,
        response,
      };

      return dispatch(usersOk(out));
    })
    .catch((error) => {
      const out = {
        request: args,
        error,
      };

      return dispatch(usersError(out));
    });
};

const deleteUser = (args = {}) => (dispatch, getState) => {
  const state = getState();
  const token = getAuthToken(state);

  args.token = token;

  dispatch(usersRequest(args));

  return UsersApi.deleteUser(args)
    .then((response) => {
      const out = {
        request: args,
        response,
      };

      return dispatch(usersOk(out));
    })
    .catch((error) => {
      const out = {
        request: args,
        error,
      };

      return dispatch(usersError(out));
    });
};

const resetUsers = (args = {}) => (dispatch) =>
  Promise.resolve(dispatch(usersReset(args)));
...

Generated Users API (api/users.js)

import requestCreator, { Loopback } from './request';

const request = requestCreator(Loopback);
const url = `${window.location.origin}/api/users`;

const fetchUsers = ({
  token,
  where,
  order,
  limit,
  skip,
  include,
}) =>
  request().withAuth(token)
    .where(where)
    .order(order)
    .limit(limit)
    .skip(skip)
    .include(include)
    .get(url)
    .then((response) => response.data);

const createUser = ({ token, ...params }) =>
  request().withAuth(token).post(url, params)
    .then((response) => response.data);

const updateUser = ({ token, ...params }) =>
  request().withAuth(token).patch(url, params)
    .then((response) => response.data);

const deleteUser = ({ token, id }) =>
  request().withAuth(token).delete(`${url}/${id}`)
    .then((response) => response.data);
...

Generated Users Selectors (selectors/users.js)

import utils from './utils';

const model = 'users';

const getUsers = (state, { ids } = {}) =>
  utils.getList({ state, model, ids });

const getUser = (state, key) =>
  utils.getItem({ state, model, key });

Example Scenario: Fetching Nested Data

Gig.yaml (schema definition)

Models section of gig.yaml

...
models:
  - name: user
    properties:
      - name: id
        type: number
        id: true
      - name: email
        type: string
      - name: password
        type: string
        hidden: true
        obscured: true
    relations:
      - name: platform
        type: belongsTo
        model: platform
        foreignKey: platformId
  - name: platform
    properties:
      - name: id
        type: number
        id: true
      - name: name
        type: string
        required: true
        min: 2
        max: 50
    relations:
      - name: user
        type: hasOne
        model: user
        foreignKey: userId
 ...

Users Fetch Action Creator (actions/users.js)

To fetch a user's associated platform data, pass a custom query param to the fetchUsers action creator.

const query = {
  platform: {},
};

fetchUsers({ query });

actions/users.js

A mapping is generated between associated models and their fetch action creator

const actionCreatorMap = {
  platforms: fetchPlatforms,
};

...

const fetchUsers = (args = {}) => (dispatch, getState) => {

Include filters are defined based on the query param

  if (!args.include) {
    args.include = [];
  }

  const query = args.query || {};
  delete args.query;

  const include = Object.keys(query);
  args.include = args.include.concat(include);

  /* Nested Data
   *  - platform [platform]
   */

  dispatch(usersRequest(args));

  return UsersApi.fetchUsers(args)
    .then((response) => {

This utilty call returns the requests to fetch the associated data in the query param

      const requests = fetchNestedData({
        list: response,
        dispatch,
        token,
        query,
        actionCreatorMap,
      });

      const out = {
        request: args,
        response,
      };

      requests.push(dispatch(usersOk(out)));

      return Promise.all(requests);
    })
    .catch((error) => {
      const out = {
        request: args,
        error,
      };

      return dispatch(usersError(out));
    });
};