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 🙏

© 2024 – Pkg Stats / Ryan Hefner

duxions

v0.0.1

Published

Actions and Reducers with less boilerplate, cleaner readability, and simple testing

Downloads

1

Readme

Duxions: A clear way to write action/reducer pairs.

The Standard Redux Example

After working with Redux for a while, our team at Vevo come to a common conclusion that there is a lot of boilerplate required for actions and reducers. We love avoiding setState() and instead using actions/reducers, but it can become quite tedious. Take this widget creator for example:

// ToDoActions.js
// TYPES
export const CREATE = 'my-app/toDos/CREATE';
export const UPDATE = 'my-app/toDos/UPDATE';
export const SET_ACTIVE_INDEX = 'my-app/toDos/SET_ACTIVE_INDEX';

// ACTION
export function createToDo(todo) {
  return { type: CREATE, todo };
}

export function updateToDo(todo) {
  return { type: UPDATE, todo };
}

export function setActiveIndex(activeIndex) {
  return { type: SET_ACTIVE_INDEX, activeIndex };
}
// ToDoReducer.js
import { CREATE, UPDATE, SET_ACTIVE_INDEX } from "./actions";
// REDUCER
export default function reducer(state = {toDos: []}, action = {}) {
  switch (action.type) {
    case CREATE:
      state.toDos = state.toDos.push(action.todo);
      break;
    case UPDATE:
      state.toDos = state.toDos.map(
        todo => (
          todo.id === action.todo.id ? action.todo : todo
        )
      );
      break;
    case SET_ACTIVE_INDEX:
      state.activeIndex = action.activeIndex;
      break;
    default: return state;
  }
  return state;
}

Whats difficult with this traditional approach?

  1. Its a lot of boilerplate to write.
  2. Switching between all of these files requires more cognitive load when developing.
  3. PRs are difficult when all of your code is separated like this. The related code can appear in very different places.

So we thought how could we improve this?

  1. What if we don't have to manually write types anymore?
  2. What if we put actions and reducers together so that they are more readable?

Our approach

For the basic example, we replicate the code from above in a much shorter syntax

// ToDoDuxion.js
import createActionReducer from 'duxions';

const NAMESPACE = 'app/todos';

const initialState = {
    toDos: [],
    activeToDoIndex
};

const actionReducers = {
    createToDo: {
        action: toDo => {payload: toDo}, // NO TYPE NEEDED! They get generated automatically
        reducer: state => {
            state.toDos = state.toDos.push(action.payload);
            return state;
        }
    },
    updateToDo: {
        action: toDo => { payload: toDo},
        reducer: state => {
            state.toDos = state.toDos.map(
                todo => (
                todo.id === action.todo.id ? action.todo : todo
                )
            );
            return state;
        }
    },
    // You can also just write functions that return an object that is merged into your store.
    setActiveToDoIndex: index => ({activeToDoIndex: index})
}

const actionsReducerPair = createActionReducer(NAMESPACE, initialState, actionReducers);

export const { actions } = actionsReducerPair;
export const { reducer } = actionsReducerPair;

What is it doing?

Its actually just doing Redux. It looks at the Lets walk through this:

  1. Because our actions and reducers are named keys in an object, we can auto generate our types for actions and reducers from that key. For example createToDo would have a type that looks like this: app/todos/createToDo.
  2. The reducer is automatically bound to that type. This is actually a small performance improvement over traditional switch statements, because it uses object indexing rather than a switch statement to handle the reducer.
  3. Because the reducer and actions are exported, the store and its actions can be referenced using standard redux just like any other set of action/reducer pairs.
  4. What if you want to have multiple reducers respond to the same action type?
    • This can be an anti-pattern as it makes it harder to track how changes in your codebase are affecting each other. One of the goals of this setup is to keep actions and their reducers co-located. There is a way to override this behavior though, by passing your own "type" field to the object returned by your action.

Normal action/reducer pairs:

const actionReducers = {
  setThing: {
    action: (thing) => {payload: thing}
    reducer: (state) => {state.thing = payload.thing; return state;}
  }
}

Shorthand for pure functional actions:

This shorthand should feel just as easy as using setState({mergeMe:true}) in a component. It just merges the object returned from the function into your store.

const actionReducers = {
  setThing: (thing) => ({thing}) // the returned object is merged into the store.
}

This function will merge only if it receives an object.

Asynchronous actions (thunks) shorthand

If you use the shorthand and return a function, then it will call that function with (actions, dispatch, getState);

const actionReducers = {

  // Loading State
  setLoading: (isLoading) => ({isLoading}),
  loadFail: (error) => ({ isLoading: false, error }),

  // Thing actions
  setThing: (thing) => {things: {[thing.id]: thing}}
  getThing: (thingId) => async (actions, dispatch, getState) => {
    try {
      dispatch(actions.setLoading(true));
      const thing = await myAPI.getThing(thingID);
      dispatch(actions.setThing(thing));
      dispatch(actions.setLoading(false));
    } catch (err) {
      dispatch(actions.loadThingFail(err));
    }
  },
};

Now take a second to stop and think about that previous example. How many lines of boilerplate would be needed with traditional redux? Three of our action/type/reducers were reduced to a single line of code, and with traditional redux would have each been at least 7 lines of code.