npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

classy-redux

v1.0.12

Published

Uses OOP to simplify Redux reducer logic

Readme

Classy-Redux

A "Classier" system of creating Redux reducer functions

Basic Example

Classy Redux let's you convert this (from the Redux To Do example):

const todo = (state = {}, action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return {
                id: action.id,
                text: action.text,
                completed: false
            }
        case 'TOGGLE_TODO':
            if (state.id !== action.id) {
                return state
            }

            return Object.assign({}, state, {
                completed: !state.completed
            })

        default:
            return state
    }
}
const todos = (state = [], action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return [
                ...state,
                todo(undefined, action)
            ]
        case 'TOGGLE_TODO':
            return state.map(t =>
                todo(t, action)
            )
        default:
            return state
    }
}

Into this:

class TodosBuilder extends ReducerBuilder {
    addTodo(action}, state) {
        state.push(this._buildTodo(action);
    }
    _buildTodo({id, text}) {
        return {completed: false, id, text};
    }
    _toggleCompletion(id, todo) {
        if (todo.id === id)  {
            todo.completed = !todo.completed;
        }
        return todo;
    }
    toggleTodo({id}, state) {
        return state.map(t => this._toggleCompletion(id, t));
    }
}
const todoReducer = new TodoBuilder().reducer;

How do I use it?

// Step 0: Install it:

npm install --save classy-redux

// Step 1: Import it

import {ReducerBuilder} from 'classy-redux';

// Step 2: Write your action creators as normal

let nextTodoId = 0
export const addTodo = (text) => ({type: 'ADD_TODO', id: nextTodoId++, text});
export const toggleTodo = (id) => {type: 'TOGGLE_TODO', id});

// Step 3: Extend ReducerBuilder

class TodoReducerBuilder extends ReducerBuilder {

    // Step 4: Add action handlers with names matching the action type
    //         (action handlers are passed the action and a copy of the state)
    
    addTodo(action, state) {
        state.push(this._buildTodo(action);
        // If you modify the state the action handler doesn't need to return anything;
        // that state will be returned
    }
    // (Optional) Use non-action handling helper methods
    _buildTodo({id, text}) {
        return {completed: false, id, text};
    }
    
    toggleTodo(action, state) {
        // If an action handler return a ("truthy") value that value becomes the new state
        return state.map((id, todo) {
            if (todo.id === action.id)  {
                todo.completed = !todo.completed;
            }
            return todo;
        });
    }
}

// Step 5: Instantiate your ReducerBuilder to access your new reducer function

export default new TodoReducerBuilder().reducer;

// Optional

class TodoReducerBuilder extends ReducerBuilder {

    // Optional Step #1: Use "reduction" to share state between methods:
    //                   (State only lives for one reducer cycle)
    
    addTodo(action, state) {
        this.reduction.currentAction = action;
        state.push(this._buildTodo();
    }
    _buildTodo() {
        const {id, text} = this.reduction.currentAction;
        return {completed: false, id, text};
    }

    // Optional Step #2: Use BeforeAction/AfterAction to trigger logic before/after the action handler:
    
    beforeAction(action, state) {
        this.reduction.todoInProgress = {};
        this.reduction.isUrgent = false;
        state.push(this.reduction.todoInProgress);
    }
    addTodo(action, state) {
        // this.reduction.todoInProgress wass created in beforeAction
        this.reduction.todoInProgress.completed = false;
        this.reduction.todoInProgress.id = action.id;
        this.reduction.todoInProgress.text = action.text;
    }
    addUrgentTodo(action, state) {
        // This will get used by the afterAction
        this.reduction.isUrgent = true;
        this.addTodo(action, state);
    }
    afterAction(action, state) {
        this.reduction.todoInProgress.isUrgent = this.reduction.isUrgent;
        
        // Make state immutable before returning it
        // (As with action handlers, values returned from beforeAction/afterAction become the new state)
        return Immutable.Map(state);
    }
    
    // Optional Step 3: Override build to decorate the reducer
    
    build() {
        super.build();
        // Decorate reducer using Redux Undo
        this.reducer = undoable(this.reducer, {debug: false, filter: distinctState()});
    }
    

If there's no switch/case, how does it know how to handle actions?

When a ResourceBuilder.reducer receives an action it uses the action's type to find a matching action handler. To find the handler the ReducerBuilder simply looks for a method with the same name as the action type, converted to camel case. For instance, an action{type: 'ADD_FOO_BAR'} would be handled by the ReducerBuilder's addFooBar method.

If no corresponding method can be found for a provided action, Classy Redux throws an error.

Why does addTodo have no return and looks like it's mutating state?

Before Classy Redux passes the action to its handler it ensures that the old state doesn't get modified by calling its clone method to generate a new version of the state from the previous one. By default the clone method is just (oldState) => _.cloneDeep(oldState), but you can override it to use a different clone algorithim (or none at all, if you would prefer to create a new state separately in each action handler).

Once it has cloned the previous state the new cloned state is passed (along with the action) to the action handler. If the action handler returns a "truthy" value, that value will become the new state. If the handler doesn't return a value, the reducer will instead return the cloned state object.

This allows action handlers to simply modify the provided state object in-place, without returning anything, and their changes will still be applied:

addBar(action, state) {
    state.bars.push(bar);
}

What about immutability or pre/post-processing?

Classy Redux provides two methods that you can override to add logic before or after every action handler: afterAction and beforeAction. These methods work just like an action handler, in that they are passed action and state arguments, and any truthy value they return will be used as the new state.

If you wish to use an immutability library you can override the afterAction method to apply an immutability function to the state after its action handler finishes:

afterEach(action, state) {
    return Immutable.Map(state);
}

What about reducer decorators (eg. Redux Undo)?

The ResourceBuilder build method is called in the class's constructor, and by default all it does is bind the reducer function to the ResourceBuilder instance. However, you can override this method to add logic for "decorating" your reducer:

build() {
    super.build();
    this.reducer = undoable(this.reducer, {debug: false, filter: distinctState()});
}

What if I want to share variables between beforeAction/afterAction and the action handler?

Because the reducer is bound to its ReducerBuilder, you could define properties in one method and use them in another:

beforeEach() {
    this._count = 0;
}
_doSomethingRepeatedly(things) {
    things.forEach(() => this._count += 1);
}
someAction(action, state) {
    this._doSomethingRepeatedly(action.things);
    return this._count;
}

However, the problem with doing that is that you might accidentally leave a property value from one call and have it affect another, later call. To avoid this Classy Redux recommends not defining properties directly on the ReducerBuilder instance, but instead on its reduction property. This property is automatically reset before every action is handled, so you never have to worry about "cleaning it up":

beforeEach() {
     this.reduction.count = 0;
}
_doSomethingRepeatedly(things) {
    things.forEach(() => this.reduction.count += 1);
}
someAction(action, state) {
    this._doSomethingRepeatedly(action.things);
    return this.reduction.count;
}

StoreBuilder

Reducers created from a ReducerBuilder can be used directly with Redux's createStore:

const reducer = new YourReducerBuilder().reducer;
const store = createStore(reducer);

However Classy Redux also offers an optional StoreBuilder that lets you easily combine as many resource builders and middleware as you want, in any order:

const {store} = new StoreBuilder(fooBuilder, thunk, barBuilder, bazBuilder, window.devToolsExtension);

To use the StoreBuilder you must define a stateName property for each ReducerBuilder to serve as the key for the reducer when it is passed to combineReducers. This can be set as follows:

// With the babel transformation "transform-class-properties"
 class FooResourceBuilder extends ResourceBuilder {
     stateName = 'foo';
 }
 
 // Without "transform-class-properties"
 class FooResourceBuilder extends ResourceBuilder {}
 FooResourceBuilder.prototype.stateName = 'foo';