Create customizable Rematch models that are lists, using the convention
byId
andallIds
to normalize data.
yarn add @gapless-tech/rematch-model-list
// or
npm i --save @gapless-tech/rematch-model-list
When connecting with APIs and working with lists, we are tired of rewriting very similar model structures in Redux. The mission of Rematch Model List
is to create a generic and customizable structure, so you could easily integrate your UI with your API, having a layer of state as the glue between them. For that, we made some assumptions:
- There is an API service that connects with your API. It is an on object or instance class with at least five methods:
get
,getById
,update
,create
,remove
. - The product of the
get
method must be an array. - Each item of the array is an object with a primary key -- by default, it is
id
, but you can configure it.
So you can create a model within a few lines!
Please bare in mind that this package is built on top of Rematch. So, if you are not using Rematch, you won't be able to use this lib.
- Redux docs: normalizing state shape using byId and allIds
- Rematch: a Redux framework
- Rematch Select Plugin
// store.js
import { init } from '@rematch/core';
import selectPlugin from '@rematch/select';
import createModelList from '@gapless-tech/rematch-model-list';
import * as api from 'todos-api-service';
// This will create a model with default
// initial state, reducers, effects and selectors
const todos = createModelList('todos', api);
const models = { [todos.name]: todos };
const plugins = [selectPlugin()];
const store = init({ plugins, models });
export const { select } = store;
export default store;
IMPORTANT: if you want to use the selectors, you must install the @rematch/select plugin.
// Todos.js
import React from 'react';
import { connect } from 'react-redux';
import { select } from './store';
const Todos = ({ todos, getTodos }) => (
// ...your component content
);
const mapState = state => ({
// The list method is a selector generated by createModelList
todos: select.todos.list(state)
});
const mapDispatch = ({ todos }) => ({
// The getAsync method is a effect generated by createModelList
getTodos: todos.getAsync,
});
export default connect(mapState, mapDispatch)(Todos);
createModelList(
// The model name
'fruits',
// This is the API service instance. You must provide this interface.
{
get: (params) => [{ key: 1, name: 'Potatoes' }, { key: 2, name: 'Bananas' }],
getById: (id, params) => { key: 1, name: 'Potatoes' },
create: (data) => any,
update: (id, data) => any,
remove: (id, params) => any
},
// Customization
{
// We are using key in this case, because our fake API
// provides objects with key instead of id as primary key
idKey: 'key',
// This will create two lists of ids inside of our reducer,
// so we could store the response of gets with different params, for example
listNames: ['allIds', 'availableFruitsIds'], // default is ['allIds']
// Add more properties to the initial state
initialState: { whatever: true, fruitsAre: 'good' },
// Add more reducers, a la Rematch
reducers: { incremement: (state, payload) => state + payload },
effects: (dispatch, baseEffects) => ({
/** YOUR EFFECTS HERE
* Notice that you can call the base effects, such as:
* */
async customGetAsync: ({ params }) => {
// Do something and then
await baseEffects.getAsync({ params });
}
}),
// Add more selectors, a la @rematch/select
selectors: { /** ...your custom selectors */ }
}
);
It will trigger get
method from your API with provided params and then
dispatch an action to write it into your model
- parameter:
{ params: any, listName: string = listNames[0], onSuccess: function = noop, onFail: function = noop, onFinish: function = noop, }
- example:
dispatch.todos.getAsync({ params: { page: 10 }, onSuccess: console.log, })
It will trigger getById
method from your API with provided params and then
dispatch an action to write it into your model
- parameter:
{ id: any, params: any, onSuccess: function = noop, onFail: function = noop, onFinish: function = noop, }
- example:
dispatch.todos.getByIdAsync({ id: 37, params: { active: true }, onSuccess: console.log, })
It will trigger create
method from your API with provided params and may
dispatch an action to refresh your model list after creation
- parameter:
{ data: any, listName: string = listNames[0], updateList: boolean, onSuccess: function = noop, onFail: function = noop, onFinish: function = noop, }
- example:
dispatch.todos.createAsync({ data: { name: 'Kiwis' }, updateList: true, onSuccess: console.log, })
It will trigger update
method from your API with provided params and may
dispatch an action to refresh your model list after creation
- parameter:
{ data: object, listName: string = listNames[0], updateList: boolean, onSuccess: function = noop, onFail: function = noop, onFinish: function = noop, }
- example:
dispatch.todos.updateAsync({ data: { name: 'Apples', key: 2 }, updateList: true, onSuccess: console.log, })
It will trigger remove
method from your API with provided params and may
dispatch an action to refresh your model list after creation
- parameter:
{ id: any, params: any, listName: string = listNames[0], updateList: boolean, onSuccess: function = noop, onFail: function = noop, onFinish: function = noop, }
- example:
dispatch.todos.removeAsync({ id: 2, params: { softDelete: true }, updateList: true, onSuccess: console.log, })
Get a list from your modelList.
- parameters:
- state
- listName (default: listNames[0])
- usage:
select.todos.list(state)
Get an item from your modelList.
- parameters:
- state
- id
- usage:
select.todos.getById(state, '33')
Clone this repo and go to folder example
, then run npm start
or yarn start
. This will open an example with a todo list.
- Testing
- Reducer documentation
MIT © gapless-tech