I need more powerful tool for create reducers and actions. But I did not find such tool.

Andreev Vladimir
Jul 30, 2017 · 5 min read

TL;DR: In my last project I needed a helper tool for Redux, which can build deep state easily and allows you mixin your logic. I worked with some tools, for example redux-actions. But all of these tools don`t help you extends or mixin different part of your logic. Actually they allow you, but don`t help.

So I created a small library for help to create reducers and actions. It is “redux-parts”.


redux-parts api consists from one exported function “Creator”.

This function takes a one argument which called “part” and return you reducer and actions.

“part” is just an object with some special properties.

In redux-parts we have two abstractions: SimplePart and ComplexPart. At first we talk about SimplePart.

SimplePart has three properties: initial_state, reducer and actions.

Part 1. Simple part.

Let`s take the simplest example(without actions) from Redux docs. It`s counter:

export default (state = 0, action) => {  
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}

With redux-parts that code will look like this:

import { Creator } from 'redux-parts';const counter_part = {
initial_state: {
value: 0
},
reducer: {
increment(state) {
return { value: state.value + 1 };
},
decrement(state) {
return { value: state.value - 1 };
},
},
};
const { actions, reducer } = Creator(counter_part);

Part 2. Simple Part, but harder.

Also there is TODO application example(without actions again) in Redux docs:

const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
case 'TOGGLE_TODO':
return state.map(todo =>
(todo.id === action.id)
? {...todo, completed: !todo.completed}
: todo
)
default:
return state
}
}

const visibilityFilter = (state = 'SHOW_ALL', action) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter
default:
return state
}
}

With redux-parts that code will look like this:

import { Creator } from 'redux-parts';const todos_part = {
initial_state: {
todos: [],
filter: 'SHOW_ALL',
},

reducer: {
addTodo(state, action) {
const { id, text } = action.payload;

return {
...state,
todos: [
...state.todos,
{ id, text, completed: false },
],
};
},

toggleTodo(state, action) {
const { id } = action.payload;
const new_todos = state.todos
.map(todo =>
(todo.id === id)
? {...todo, completed: !todo.completed}
: todo
);

return {
...state,
todos: new_todos
};
},

setVisibilityFilter(state, action) {
return {
...state,
filter: action.payload,
}
}
},
};
const { actions, reducer } = Creator(todos_part);
// How to use
const todo = { id: 1, text: 'new task' };
const addTodoAction = actions.addTodo(todo);
const state = reducer(undefined, addTodoAction);
// state.todos.length === 1;

By default the actions object consist of functions, which take the first argument and put it into the payload property in action object. Them names will correspond with names in reducer property. Why payload?

Part 3. Last part about Simple Part.

There is a problem with example above. We need to set id property for todo object. But we want to set id property automatically for our todo.

There is an action creator in Redux docs for that:

let nextTodoId = 0;export const addTodo = text => {
return {
type: 'ADD_TODO',
id: nextTodoId++,
text
}
}

redux-parts have a special property in part for such cases, it`s the actions property:

let nextTodoId = 0;const todos_part = {
// code from example above

actions: {
addTodo(text) {
return {
id: nextTodoId++,
text,
}
}
}
};
const { actions, reducer } = Creator(todos_part);
// How to use now
const addTodoAction = actions.addTodo('new task');
const state = reducer(undefined, addTodoAction);
// state.todos[0].id === 1;

As can be seen the name property in actions object must be equal to the name in reducer object. Also you need to know that all that you will return from your function, will be in payload property, so we don`t need to change code in addTodo reducer.


We have finished with SimplePart. And now we`ll take a look to ComplexPart.

Difference between SimplePart and ComplexPart is only in two properties. ComplexPart may have “simple_parts” and “complex_parts” properties.

Part 4. Complex Part. Mixins.

Sometimes we have logic which we wanna share between our sub states. Imagine we have many different collections: products, favorites, similars and etc. And each of them have the same logic: load data, load more data, clean up data and etc. In this case we can create simple partlist_part” and use it in complex parts:

const list_part = {
initial_state: {
is_pending: false,
data: null
},
reducer: {
loadData(state) {
return {
...state,
is_pending: true,
}
},
loadDataSuccess(state, { payload }) {
return {
...state,
is_pending: false,
data: payload.data,
}
}
// rest of the code
}
};
const products_part = {
simple_parts: [
list_part,
]
};
const { actions, reducer } = Creator(products_part);
const loadDataAction = actions.loadData();
const state = reducer(undefined, loadDataAction);
// state.is_pending === true;

As can be seen “simple_parts” property works like a mixin. We just add functionality of “list_part” to “products_part”.

The main idea about “simple_parts” is that you can redefine any functionality:

const simple_part = {
initial_state: {
value: 0,
text: '',
},
reducer: {
setText(state, { payload }) {
return {
...state,
text: payload.text,
}
},
setValue(state, { payload }) {
return {
...state,
value: payload.value,
}
},
}
};
const complex_part = {
initial_state: {
value: 10,
},
reducer: {
setText(state, { payload }) {
return {
...state,
text: `new text: ${payload.text}`,
}
}
},
simple_parts: [
simple_part,
]
};
const { actions, reducer } = Creator(complex_part);
let state = reducer(undefined, actions.setText('test'));
// state.value === 10;
// state.text === 'new text: test';
state = reducer(undefined, actions.setValue(5));
// state.value === 5;

In example above we redefine setText reducer and value property in initial_state.

Because “simple_parts” property is array you can mixin any number of simple parts into your complex part.

Part 5. Complex Part. Deep state.

In the begining of this article I said that redux-parts allows you create deep state easily.

The “complex_parts” property takes a dictionary of “parts” and makes sub states. Let`s take “counter_part” from the first example and create a state with two independent counters:

const complex_part = {
complex_parts: {
first: counter_part,
second: counter_part,
}
};
const { actions, reducer } = Creator(complex_part);
let state = reducer(undefined, actions.first.increment());
// state.first.value === 1;
// state.second.value === 0;
state = reducer(undefined, actions.second.increment());
// state.first.value === 1;
// state.second.value === 1;

You can create a complex part with the “complex_parts” property and use this in another complex part, thereby create any depth of state.


If you have interested you can read more about my library on github.

It`s not for production, although I used it in my last project at work and everything was ok. But I hope to develop the library together with the community, so if you have some issues or you wanna make a pull request do it, just do it.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade