1.0.0 • Published 5 years ago

redux-adjunct v1.0.0

Weekly downloads
4
License
MIT
Repository
github
Last release
5 years ago

Redux-adjunct

An opinionated set of utilities to make redux applications easier to maintain. Redux-adjunct reduces the boilerplate required to write reducers and encourages developers to organize their code around features instead of splitting code based on technical details.

It encourages functional programming by exporting only pure and curried functions.

Motivation

Redux is a great tool, too often underestimated. Recently, we've seen many people move away from Redux, as it has a reputation for being cumberstone and hard to get into. The boilerplate required to create action types, actions and reducers can be frustrating. This is attempt at reducing this boilerplate by providing a set of simple tools that make writing Redux apps faster. It also enforces correctness and documentation for actions and action types.

I belive concision is its own reward, so the library is highly motivated by the satisfaction of being able to write reducers in very few lines.

Install

npm install redux-adjunct
yarn add redux-adjunct

Write action creators and enforce corectness

Redux-adjunct allows you to create namespaced action types and their associated action creators in a single line.

import { createAction } from 'redux-adjunct';

// All actions related to a single feature are namespaced so they can be eaily debugged using devtools
const action = createActionSpec('user');

const [SET_USERNAME, setUsername] = createAction('SET_USERNAME');
const [SET_EMAIL, setEmail] = createAction('SET_EMAIL');

setUsername('gbogard') // => { type: 'user.SET_USERNAME', payload: 'gbogard' }

Validating the payload of your actions

In a weakly typed language like Javacript, it's common to forget a property or to put a number in place of a string. Redux-adjunct can enforce payload correctness at runtime to catch most of those errors during development. Note that this feature is completely opt-in.

The createActionSpec function works just like createAction, except it accepts one more argument in the form of a predicate. You need to provide a function that given the action's payload will return either true or false to indicate whether the payload is correct. You can put any validation logic you'd like, put redux-adjunct provides several ready to use predicates for you.

import { createActionSpec, isNumber, isString } from 'redux-adjunct';

const action = createAction('user');

// Age must be a number
const [SET_AGE, setAge] = createAction('SET_AGE', isNumber);

// Username must be a string
const [SET_USERNAME, setUsername] = createAction('SET_USERNAME', isString);

setUsername('gbogard') // => { type: 'user.SET_USERNAME', payload: 'gbogard' }
setUsername(42) // Throws an exception in development mode

Redux-adjunct provides a helpful hasShape predicate that can enforce a predicate for each key of an object.

import { createActionSpec, isNumber, isString, hasShape } from 'redux-adjunct';

const action = createAction('user');
const [SET_USER, setUser] = createAction('SET_USER', hasShape({
  username: isString,
  age: isNumber,
}));

Combining predicates with logical combinators

Redux-adjunct provide and and or functions that allows you to combine predicates into a single one.

import { createActionSpec, hasShape, isNumber, isUndefined, or } from 'redux-adjunct';

const action = createAction('user');
const [SET_USER, setUser] = createAction('SET_USER', hasShape({
  age: or(isNumber, isUndefined), // Age is optional
}));

Redux-adjunct comes with various types predicates like isString, isFunction, isBoolean that also come in the form isStringOrUndefined, isFunctionOrUndefined, isBooleanOrUndefined.

and allows you to specify multiple requirements for your payload

import { createActionSpec, isString, hasMinlength, and } from 'redux-adjunct';

const action = createAction('user');
const [SET_USERNAME, setUserName] = createAction('SET_USERNAME', and(isString, hasMinlength(8)));

Write concise reducers with less effort

A reducer is merely a function that takes an action and the current state and returns a new state. The logic inside a reducer can be anything as long as the function remains pure. Howevwer, a very common pattern in Redux is to switch over the action's type and return the appropriate state for each action :

const initialState = {
  isOpen: false,
}

function myReducer(state = initialState, { type, payload }) {
  switch (type) {
    case OPEN:
      return { ...state, isOpen: true };
    default:
      return state;
  }
}

Redux-adjunct allows you to write reducers as a map from action types to pure functions, elminating the need for the verbose switch syntax.

import { createReducer, setter } from 'redux-adjunct';

const initialState = {
  isOpen: false,
}

const myReducer = createReducer(initialstate, {
  [OPEN]: state => ({ ...state, isOpen: true }),
})

Functions in reducers receive both the current state and the payload of the action as arguments.

Combined with a functional programming library like Ramda, you get the ability to write powerful transforms in a very clean and concise manner :

import { createReducer, setter } from 'redux-adjunct';
import { evolve, not } from 'ramda';

const initialState = {
  isOpen: false,
}

const myReducer = createReducer(initialstate, {
  [TOGGLE]: evolve({ isOpen: not }),
})

In this case, evolve({ isOpen: not }) is equivalent to state => ({ ...state, isOpen: !state.isOpen }).

Setters

The setter method returns a method that, given a state and a payload, will set a single property of your state to the payload. Its signature allows for direct use in reducers created with createReducer.

You can use it on a top-level property :

createReducer(initialstate, {
  [SET_PROFILE]: setter('profile')
})

Or you can update a path :

createReducer(initialstate, {
  [SET_USERNAME]: setter('profile', 'details', 'username')
})

Note that when dealing with arrays you can also use the index of the element you wish to update.

Focus on features

Through concision, redux-adjunct enables to put action types, action creators and reducers together in a single file based on the feature they relate to. This allows to think in a feature-centric way, where instead of having a hierarchy that looks like this :

- actionCreators
--- users
--- pictures
--- videos
- actionTypes
--- users
--- pictures
--- videos
- reducers
--- users
--- pictures
--- videos

You can make your file structure look like this :

- users
- pictures
- videos

No more folding and unfolding to see all your files when your application grows. Everything is at the write place. This encourages developers to think of features as self-sufficient modules that can be taken in or out at any given time.

Running tests

npm run test

1.0.0

5 years ago

0.2.1

5 years ago

0.2.0

5 years ago

0.1.0

5 years ago