/redux-companion

Opinionated way to reduce boilerplate on async (or sync) redux logic, like fetching data etc. Zero dependency.

Primary LanguageTypeScriptMIT LicenseMIT

Redux Companion

GitHub GitHub package version npm bundle size (minified + gzip) npm Travis Codecov FOSSA Status

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).

Packages

Installation

npm i redux-companion

Why?

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?

Quick Start

See example usage.

Let's recreate Redux Todo List example with redux-companion and Ducks pattern.

Reducers

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.

License

FOSSA Status