0.0.3 • Published 5 years ago

redux-entity-manager v0.0.3

Weekly downloads
7
License
MIT
Repository
github
Last release
5 years ago

Redux Entity Manager

Build Status

A framework-agnostic library for easy domain entity management

Demo

React with Redux and Thunk

Coming soon: demo and examples using React with redux-observable and Angular with ngrx.

Prerequisites

  1. Any Redux library such as Redux, ngrx or custom implementation.
  2. Any side-effect library such as Redux Thunk, redux-observable, ngrx or custom implementation.
  3. Domain entities must have a primary key.

Installation

yarn add redux-entity-manager

or

npm install redux-entity-manager

Motivation

In some Redux tutorials you have probably seen code that looks like this:

const initialState = {
    selectedId: null,
    todos: [
        {
            id: 1,
            name: 'Foo',
        },
    ],
};

While this works fine for a simple project, in real life applications it will cause problems because:

  • the state is not normalized
  • client state and domain entities are mixed together.

A better state structure would look like this:

{
    "entities": {
        "todo": {
            "1": {
                "id": 1,
                "name": "Foo"
            }
        }
    },
    "pages": {
        "account": {
            "todos": {
                "selectedId": null
            }
        }
    }
}

To create our root reducer for this state structure we'd probably write something like this:

import { combineReducers } from 'your-favourite-redux-library';

const rootReducer = combineReducers({
    entities: {
        todo: todoReducer,
        user: userReducer,
        // Other entity reducers
    },
    // Other reducers
});

Notice that we have to write a separate reducer with its own actions and selectors for each entity. Again, this is fine for simple projects, but what if we have dozens of domain entities? A possible solution would be using a reducer factory:

import { combineReducers } from 'your-favourite-redux-library';

const rootReducer = combineReducers({
    entities: {
        todo: makeEntityReducer('todo'),
        user: makeEntityReducer('user'),
        // Other entity reducers
    },
    // Other reducers
});

This approach has one more serious advantage: we can now create reusable components such as data tables, forms, etc.

React example:

<DataTable entityName="todo" columns={['id', 'name']} />
<DataTable entityName="user" columns={['id', 'name', 'address', 'phone']} />

Angular example:

<app-data-table entityName="todo" [columns]="['id', 'name']"></app-data-table>
<app-data-table entityName="user" [columns]="['id', 'name', 'address', 'phone']"></app-data-table>

Basics

Redux Entity Manager provides a reducer factory, action creators and selectors. All action creators and selectors have two mandatory parameters: entityName and query which are probably best explained by examples:

const entityName = 'user';
const query = null;
const action = makeReadRequestAction(entityName, query);
const users = entitiesSelector(state, { entityName, query });
const entityName = 'user';
const query = {
    id: 1,
};
const action = makeReadRequestAction(entityName, query);
const users = entitiesSelector(state, { entityName, query });
const entityName = 'user';
const query = {
    page: 1,
    limit: 10,
};
const action = makeReadRequestAction(entityName, query);
const users = entitiesSelector(state, { entityName, query });
const entityName = 'user';
const query = {
    banned: true,
};
const action = makeReadRequestAction(entityName, query);
const users = entitiesSelector(state, { entityName, query });

Think of how you write a SQL query:

SELECT * FROM users WHERE id = 1

or

SELECT * FROM users LIMIT 10

In Redux Entity Manager, entityName corresponds to table name in FROM clause, and query to everything else. In fact, much like most RDBMSs, Redux Entity Manager will create an index for each query to avoid storing duplicate entities.

Please note that Redux Entity Manager only handles data in Redux store. It is your responsibility to write side effects, i.e. http requests. This is an intentional design decision that allows using

  • any side effects library (e.g. Redux Thunk, redux-observable, ngrx, etc...)
  • any architecture (REST, GraphQL, etc...)
  • any additional logic (pagination, cache invalidation, undo/redo, etc...)

Tutorials and Examples

Contributing

Improvements and bugfix PRs are welcome!

If you have an idea for a feature that would break API or tests, please open a discussion before submitting a PR.