@gigster/module-redux-models
v2.0.0
Published
Role | Name | Email | Slack ---- | ---- | ----- | ----- *Product Owner* | Frances Haugen | [[email protected]](mailto:[email protected]) | [@frances] *Maintainer* | Geoff Kinder | [[email protected]](mailto:[email protected]) | [@geoff]
Downloads
10
Readme
redux-models
Role | Name | Email | Slack ---- | ---- | ----- | ----- Product Owner | Frances Haugen | [email protected] | [@frances] Maintainer | Geoff Kinder | [email protected] | [@geoff] Developer | Alice Wang | [email protected] | [@alicebot] Developer | Dan Isaza | [email protected] | [@dan] Developer | Mark Miyashita | [email protected] | [@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
Name | Status | Description
---- | ------ | -----------
generateTests
| true
/false
| Set 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 herereact-base
module, located hereredux-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 flightUSERS_OK
: dispatched when a request is successful to track the response and denote that a request is completeUSERS_ERROR
: dispatched when a request has errored to track the error responseUSERS_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));
});
};