nuba / autodux

Automate the Redux boilerplate.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Autodux

Automate the Redux boilerplate.

Status: Developer preview

This library is still in the idea phase. Please check it out and file any issues you encounter.

Known Issues

  • Currently requires ES6 features. Should work great in any modern evergreen browser, but has no support for old browsers (e.g., IE).

Install

npm install --save autodux

And then in your file:

import autodux from 'autodux';

Or using CommonJS syntax:

const autodux = require('autodux');

Why?

Redux is great, but you have to make a lot of boilerplate:

  • Action constants
  • Action creators
  • Reducers
  • Selectors

It's great that Redux is such a low-level tool. It's allowed a lot of flexibility and enabled the community to experiment with best practices and patterns.

It's terrible that Redux is such a low-level tool. It turns out that:

  • Most reducers spend most of their logic switching over action types.
  • Most of the actual state updates could be replaced with generic tools like "concat payload to an array", "remove object from array by some prop", or "increment a number". Better to reuse utilities than to implement these things from scratch every time.
  • Lots of action creators don't need arguments or payloads -- just action types.
  • Action types can be automatically generated by combining the name of the state slice with the name of the action, e.g., counter/increment.
  • Lots of selectors just need to grab the state slice.

Lots of Redux beginners separate all these things into separate files, meaning you have to open and import a whole bunch of files just to get some simple state management in your app.

What if you could write some simple, declarative code that would automatically create your:

  • Action type constants
  • Reducer switching logic
  • State slice selectors
  • Action object shape - automatically inserting the correct action type
  • Entire action creators if no payload is required

Turns out, when you add this simple logic on top of Redux, you can do a lot more with a lot less code.

import autodux from 'autodux';

// This can be used for action creators that pass
// a single argument through as the payload,
// and also for selectors that just select the
// whole reducer state.
const id = x => x;

const counter = autodux({
  // the slice of state your reducer controls
  slice: 'counter',

  // The initial value of your reducer state
  initial: 0,

  // No need to implement switching logic -- it's
  // done for you.
  actions: {
    increment: {
      reducer: state => state + 1
    },
    decrement: {
      reducer: state => state - 1
    },
    multiply: {
      create: id,
      reducer: (state, payload) => state * payload
    }
  },

  // No need to select the state slice -- it's done for you.
  selectors: {
    getValue: id
  }
});

What you get from that is an object that looks like this:

{
  initial: 0,
  actions: {
    increment: { [Function]
      type: 'counter/increment'
    },
    decrement: { [Function]
      type: 'counter/decrement'
    },
    multiply: { [Function]
      type: 'counter/multiply'
    }
  },
  selectors: {
    getValue: [Function: wrapper]
  },
  reducer: [Function: reducer]
}

Let's explore that object a bit:

const {
  selectors: { getValue },
  actions: {
    increment,
    decrement
  },
  reducer,
  initial
} = counter;

const actions = [
  increment(),
  increment(),
  increment(),
  decrement()
];

const state = actions.reduce(reducer, initial);

console.log(getValue({ counter: state })); // 2
console.log(increment.type); // 'counter/increment'

API Differences

Reducers and selectors have simplified APIs.

Reducers

Switching over different action types is automatic, so we don't need an action object that isolates the action type and payload. Instead, we pass the action payload directly, e.g:

With Redux:

const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const MULTIPLY = 'MULTIPLY';


const counter = (state = 0, action = {}) {
  switch (action.type){
    case INCREMENT: return state + 1;
    case DECREMENT: return state - 1;
    case MULTIPLY : return state * action.payload
    default: return state;
  }
};

With Autodux, pass the reducer cases one at a time, and they'll be switched over automatically:

const counter = autodux({
  slice: 'counter',
  //... stuff here
  actions: {
    increment: {
      reducer: state => state + 1
    },
    decrement: {
      reducer: state => state - 1
    },
    multiply: {
      create: id,
      reducer: (state, payload) => state * payload
    }
  }
  // ... other stuff
});

Autodux creates the action types for you automatically, and eliminates the need to write switching logic or worry about (or forget) the default case. Reducers don't deal directly with the action object. Instead, they're passed the payload directly.

Selectors

Selectors are designed to take the application's complete root state object. The slice you care about is automatically selected for you, so you can write your selectors as if you're only dealing with the local reducer.

This has some implications with unit tests. The following selector will just return the local reducer state:

const counter = autodux({
  // stuff here
  selectors: {
    getValue: state => state
  },
  //other stuff

In your unit tests, you'll need to pass the key for the state slice to mock the global store state:

test('counter.getValue', assert => {
  const msg = 'should return the current count';
  const { getValue } = counter.selectors;

  const actual = getValue({ counter: 3 });
  const expected = 3;

  assert.same(actual, expected, msg);
  assert.end();
});

About

Automate the Redux boilerplate.

License:MIT License


Languages

Language:JavaScript 100.0%