ORM-like API to access Redux store state
In Redux your reducer can not change store state directly. Instead, you write this:
case EDIT_TODO:
return {
todos: this.todos.map(todo =>
todo.id === action.payload.id ?
Object.assign({}, todo, { text }) :
todo
)
}
ImmutableJS provides another approach to it. Beeing effective, it is still not readable:
case EDIT_TODO:
return todos.map(t => {
if (t.get('id') === action.payload.id) {
return t.set('text', text);
} else {
return t;
}
});
In particular, it makes you use ImmutableJS objects instead plain JS objects.
Redux-entity-store solves this by modeling store state as the relational database and providing ORM-like API to access it. Above example could be rewritten as:
case EDIT_TODO:
const todo = session.todo.byId(id);
todo.text = text;
(Session is an entry point to the API, will be discussed later).
Looks much simpler, right?
NOTE: further code examples will be given using redux-action-object.
Your store state should contain a property named 'data'. Part of the state tree, starting with data, will be managed by redux-entity-store. Data is an object whose properties are tables. Each table is an array of objects called entities. Each entity should have a property named 'id'.
class TodoModel { // Initial state using redux-action-object
data = {
todo: [{
id: 0,
text: 'Use Redux',
completed: false
}]
};
...
}
Those methods that wish to use redux-entity-store are annotated with @entityStore.data annotation. They will get an extra argument 'session'. Session is an entry point to data API:
import * as entityStore from 'redux-entity-store';
class TodoModel {
...
@entityStore.data
edit(session, id, text) { // Reducer function
const todo = session.todo.byId(id);
todo.text = text;
}
}
Bootstrap and UI code looks the same as in redux-action-object:
import * as actionObject from 'redux-action-object';
const { actionCreators, reducer } = actionObject.split(new TodoModel());
const store = createStore(reducer);
const actions = actionObject.bind(actionCreators, store.dispatch);
@connect(state => ({ todos: state.todos }))
class TodosComponent extends React.Component {
handleSave(id, text) {
actions.edit(id, text);
}
}
Redux-entity-store intercepts reducers functions annotated with @data. All entities retrieved via session and modified are put back into the store on return from the reducer.
Usually, you will want to bind to a specific subset of data in your UI component. Memoized selectors using Reselect provides a handy and fast method to do it. See example:
import { createSelector, createStructuredSelector } from 'reselect';
const data = state => state.app.data;
const todos = createSelector(data, data => data.todos);
const selectedTodoId = (state, props) => props.params.id;
const selectedTodo = createSelector(todos, selectedTodoId, (todos, selectedTodoId) => (
todos.filter(todo => todo.id == selectedTodoId)[0]
));
@connect(createStructuredSelector({
selectedTodo
}))
class EditTodo extends React.Component {
handleUpdate(id, text) {
actions.edit(id, text);
}
}
For certain projects, Redux state can contain the same entities as the server side. For such cases, redux-entity-store can generate requests to update server-side data.
To use server side support, backend must accept entity updates in the format:
[
{
type: 'CREATE', // possible values are CREATE, UPDATE, DELETE
table: 'todo', // mandatory table name
fields: { id: 1, text: 'text' } // id field is required
},
{
type: 'DELETE',
table: 'todo',
fields: { id: 1 } // only id will be sent
},
{
type: 'UPDATE',
table: 'todo',
fields: { id: 1 } // id is required
}
]
Example of the server side code is in todo-demo-server.js.
The server part of the demo app requires Mysql to be installed. For the MacOS and Homebrew, use:
brew install mysql
mysql.server restart
then
mysql -u root
> create database todo;
> grant all privileges on todo.* to 'todo'@'localhost' identified by 'todo';
To start server part, use
npm run start:server
Then in a different terminal start front-end part
npm run start
Open http://localhost:3000/ to access demo app.
All demo data will be saved into Mysql. You can data dump by opening http://localhost:3001/data.
TBD, see the source for now.
Several other libraries are doing similar things. Most notable are redux-schema and redux-orm.
In contrast to above libs, redux-entity-store doesn't require you to define a schema for your data. Instead you work with plain JSON objects.
In addition, Redux-orm trends towards the full-fledged ORM: relations between entities, batched updates, cascade deletes etc (think Hibernate). But I believe most projects could benefit from less powerful but simpler model.
Redux-schema does its job by completely abstracting from reducers. Thus each property change becomes a Redux action and reducer invocation. This code snippet
user.login = 'michael';
user.name = 'Michael Kane';
generates two Redux actions, for each property change.
Such complete abstraction makes you loose benefits of the Redux architecture like separation of reducers (application logic) from the UI components (presentation logic).
MIT. See LICENSE