npm package: @gapless-tech/rematch-model-list
Description: Create customizable Rematch models that are lists, using the convention byId and allIds
Author: gapless-tech
Latest version: 1.0.7

# rematch-model-list

> Create customizable Rematch models that are lists, using the convention `byId` and `allIds` to normalize data.

## Install

```bash
yarn add @gapless-tech/rematch-model-list
// or
npm i --save @gapless-tech/rematch-model-list
```

## Concepts

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.

## References
- [Redux docs: normalizing state shape using byId and allIds](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape)
- [Rematch: a Redux framework](https://github.com/rematch/rematch)
- [Rematch Select Plugin](https://github.com/rematch/rematch/tree/master/plugins/select)

## Usage

```js
// 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.

```jsx
// 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);
```

## Instantiation
```js
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 */ }
  }
);
```

## Base effects

### .getAsync()

It will trigger `get` method from your API with provided params and then
dispatch an action to write it into your model
- parameter:
  ```ts
    {
      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,
  })
  ```

### .getByIdAsync()

It will trigger `getById` method from your API with provided params and then
dispatch an action to write it into your model
- parameter:
  ```ts
    {
      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,
  })
  ```

### .createAsync()

It will trigger `create` method from your API with provided params and may
dispatch an action to refresh your model list after creation
- parameter:
  ```ts
    {
      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,
  })
  ```

### .updateAsync()

It will trigger `update` method from your API with provided params and may
dispatch an action to refresh your model list after creation
- parameter:
  ```ts
    {
      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,
  })
  ```

### .removeAsync()

It will trigger `remove` method from your API with provided params and may
dispatch an action to refresh your model list after creation
- parameter:
  ```ts
    {
      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,
  })
  ```


## Base selectors

### .list()

Get a list from your modelList.
- parameters:
  - state
  - listName (default: listNames[0])
- usage:
  ```
  select.todos.list(state)
  ```

### .getById()

Get an item from your modelList.
- parameters:
  - state
  - id
- usage:
  ```
  select.todos.getById(state, '33')
  ```

## Example of application

Clone this repo and go to folder `example`, then run `npm start` or `yarn start`. This will open an example with a todo list.

## TODO
  - Testing
  - Reducer documentation

## License

MIT © [gapless-tech](https://github.com/gapless-tech)