Home

Awesome

Duckfactory

Redux ducks, even simpler

Redux / react redux are pretty great, but as the code grows, things easily get cumbersome, complex and fragmented. Redux ducks is an interesting proposal for handling this: organizing the code in bundles of action creators, action types and reducers that belong together, in the very common cases where there's a one-to-one (-to-one) relationship between them.

Duckfactory is a library for wrapping and simplifying this even more. It auto-generates action creators and action types and encapsulates them, only exposing them when needed for side purposes like unit testing. A lot of the boilerplate is abstracted away, the project code gets more minimal, clean and readable, and you, the brilliant developer, gets happier.

How does it work?

In short, give it a prefix string to group the actions, an initial state, an object with the names of action creators and the reducers the actions should trigger. It will then create an object that exposes ordinary redux action creators, reducers and action types.

Example

Let's define a reducer in an example file users.js:

import DuckFactory from 'duckfactory';

// Just an example initial redux state (userId → userObject):
const initialState = {
    42: {
        name: "Arthurarthurdent",
        age: "30-ish",
        occupation: "Sandwich maker"
    },
};

// Defining two ducks in the collection 'myapp/userducks':
const userDucks = new DuckFactory("myapp/userducks", initialState, {
    setName: (state, {userId, newName}) => {
        const newUser = {...state[userId]};
        if (!newUser) {
            return state;
        }
    	return {
    	    ...state,
    	 	[userId]: {
    	    	...newUser,
    	    	name: newName
    	 	}
    	}
    },
    
    addUser: (state, {userId, name, age, occupation}) => {
    	return {
    	    ...state,
    	    [userId]: { name, age, occupation }
    	}
    }
});

About the syntax here: I prefer the ES6 arrow notation and destructuring the action argument. But the more classic syntax like this should also work fine, so you can define it like this instead:

	///...
	
    setName: function (state, action) {
        const newUser = {...state[action.userId]};
        if (!newUser) {
            return state;
        }
    	return {
    	    ...state,
    	 	[action.userId]: {
    	    	...newUser,
    	    	name: action.newName
    	 	}
    	}
    },
    
    addUser: (state, action) => {
    	return {
    	    ...state,
    	    [action.userId]: { 
    	        name: action.name, 
    	        age: action.age, 
    	        occupation: action.occupation 
    	    }
    	}
    }
    
    ///...

Both of these syntaxes are fine: the action creators are autogenerated in runtime, and the action creator arguments are deduced from the arguments in the reducer functions in the definition. That way, once you've defined it as above, you can get the action creators and use them directly:

const actionCreators = userDucks.getActionCreators();

const action1 = actionCreators.setName("Arthur Dent");
const action2 = actionCreators.addUser(43, "Marvin", "Will it never end?", "Paranoid android");

In this example, these two actions will look like this, ready to dispatch and will trigger regular reducers:

console.log(action1);
//    {
//      type: "myapp/userducks/setName",
//      newName: "Arthur Dent"
//    }


console.log(action2);
//   {
//     type: "myapp/userducks/addUser",
//     userId: 42,
//     name: "Marvin",
//     age: "Will it never end?",
//     occupation: "Paranoid android",
//  }

Hooking it up: exporting to combine reducers

If you've used redux before, this should be simple. At the bottom of users.js, export the necessary:

export default userGeese.getReducers();
export const actionCreators = userGeese.getActionCreators();

...and then use the default export in combinereducers as usual, with other duckfactories if you like:

import { combineReducers } from 'redux';

import userReducer from './users'; // <-- This is the default export from above
import gameReducer from './game';

export default combineReducers({
    game: gameReducer,
    users: userReducer
});

I haven't tried, but I can't see any reason it shouldn't work to mix reducers from duckfactories with reducers that are created in other ways, e.g. regular redux if you need to. You probably want to make sure the action names are all unique, though.

Constructor:

new DuckFactory(actionTypePrefix, initialState, actionAndReducerMap, checkAndWarn, logBuilt)

Exposed after creation:

After creation, the resulting duck exports as JS objects:

Installation

npm install duckfactory --save

or

yarn add duckfactory

NOTE: if your app uses minification/uglification, version 1.3.0 should be okay, but don't use the versions below that. My own testing has been done with webpack 2 and yarn. If your mileage varies, please let me know.

Contributions

Suggestions, improvements, corrections, bug notifications, etc... all is welcome on github or espen42@gmail.com. Special thanks to NorwegianKiwi for awesome help!

Using it with redux-sagas

The produced actions can of course be used to trigger redux-sagas too. But duckfactory can only use reducer functions, not saga generators. Luckily, here's a sibling library that does the same thing for sagas: Goosefactory. Duckfactories and Goosefactories play nicely with each other.