Opinionated way to reduce boilerplate on async (or sync) logic, like fetching data etc.
Zero dependency (Although it only makes sense to use together with Redux
AND Redux Thunk
).
npm i redux-companion
I find myself exhausted of writing (or copying) a lot of the same set of redux state, reducer, actions, and thunks for interacting with the api, so I thought why not make it easier?
See example usage.
Let's recreate Redux Todo List example with redux-companion and Ducks pattern.
reducers/todos.js
import { createAction, createReducer } from 'redux-companion';
export const addTodo = createAction('ADD_TODO');
export const toggleTodo = createAction('TOGGLE_TODO');
const handlers = {
[addTodo]: (state, payload) => [
...state,
{
id: payload.id,
text: payload.text,
completed: false
}
],
[toggleTodo]: (state, payload) =>
state.map(todo => (todo.id === payload ? { ...todo, completed: !todo.completed } : todo))
};
const todo = createReducer(handlers, []);
export default todo;
reducers/visibilityFilter.js
import { createAction, createReducer } from 'redux-companion';
export const setVisibilityFilter = createAction('SET_VISIBILITY_FILTER');
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE'
};
const handlers = {
[setVisilibityFilter]: (state, payload) => payload
};
const visibilityFilter = createReducer(handlers, []);
export default visibilityFilter;
Not too much different, eh? But for me it's slightly better and less boilerplate. How about async actions?
Let's actually save our todo list to the server.
services/api.js
import axios from 'axios';
export const putTodos = todos => axios.put(`${BASE_URL}/todos/`, todos).then(res => res.data);
reducers/todos.js
import {
createAction,
createReducer,
+ createAsyncMiddleware,
+ createAsyncStatusReducer
} from 'redux-companion';
import { putTodos } from '../services/api';
export const addTodo = createAction('ADD_TODO');
export const toggleTodo = createAction('TOGGLE_TODO');
const handlers = {
[addTodo]: (state, payload) => [
...state,
{
id: payload.id,
text: payload.text,
completed: false
}
],
[toggleTodo]: (state, payload) =>
state.map(todo => (todo.id === payload ? { ...todo, completed: !todo.completed } : todo))
};
+ export const todosMiddleware = createAsyncMiddleware({
+ [saveTodos]: async ({ dispatch, getState }) => {
+ const todos = getState().todos;
+ try {
+ await putTodos(todos);
+ // do something after save action complete
+ } catch (e) {
+ // handle error here
+ }
+ }
+ });
+ export const { actions: saveTodos, reducer: saveTodosStatusReducer } = createAsyncStatusReducer(
+ 'SAVE_TODOS'
+ );
const todo = createReducer(handlers, []);
export default todo;
And that's it, when you dispatch the saveTodos() action, the middleware (that you have to register, see example) will catch the action and run the associated api call with the todos.