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

redux-handlers

v1.0.2

Published

Compose your reducers with simple and testable functions, not switch statements.

Downloads

49

Readme

redux-handlers

Compose your reducers with simple and testable functions, not switch statements.

This library provides some simple glue-code that facilitates composing your reducers with isolated, testable functions.

💪 Usage

const { registerHandler, createReducer } = createHandlers()

export const addTodo = (todo, state) => { /* ... */ }
registerHandler('ADD_TODO', addTodo)

export const toggleTodo = (id, state) => { /* ... */ }
registerHandler('TOGGLE_TODO', toggleTodo)

/**
* NB: expects action creator to set values in same order:  
* 
*     { type: 'SET_TODO_DATE', id: 42, dueDate: Date.now() }
* 
* If you use an action creator, it's easier to keep this in sync.
*/
export const setTodoDueDate = (id, dueDate, state) => { /* ... */ }
registerHandler('SET_TODO_DUE_DATE', setTodoDueDate)

export const reducer = createReducer([])

🔥 Motivation

Redux reducers are usually written with switch statements.

export function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      // ...
    case 'TOGGLE_TODO':
      // ...
    case 'SET_TODO_DUE_DATE':
      // ...
    default:
      return state
  }
}

I never liked this, and wanted to compose my reducer out of functions:

export const addTodo = (todo, state) => { /* ... */ }
export const toggleTodo = (id, state) => { /* ... */ }
export const setTodoDueDate = (id, dueDate, state) => { /* ... */ }

// some magic... 🧙‍♀️
export const reducer = combineTheseFunctionsTogetherSomehow() 

I found switch for my reducers to be unpleasant for many reasons:

  1. They're hard to locate:
  2. You can't Navigate To Declaration from a test
  3. You can't navigate to a symbol definition
  4. You can't see the case statement in a File Structure view in an IDE
  5. Your code for ADD_TODO is separate from TOGGLE_TODO, yet they live in same function and you have to test them through the same pathway, i.e. by exercising the reducer
  6. Writing tests for a reducer system (requiring an action object) is cognitively more difficult and less pleasant to do than testing a function, so people are less likely to write them
  7. switch / case / default syntax is awkward at best and easy to mess up
  8. Adding types to action is vastly more painful than writing types for a simple function

🥳 Solution

Handlers: functions that you can use to compose reducers. A handler follows two simple rules:

  1. It is a function that takes a state at the end of its parameter list, and returns a state
  2. It takes additional parameters in the same order they were specified in the action object

Once a handler function is created, you register it with an action type to tell the reducer you want use that handler to respond to the action type.

export const addTodo = (todo, state) => [...state, todo]
registerHandler('ADD_TODO', addTodo)

For additional parameters, it just reads values in the same order they were added in the action creator object literal:

// actions.js
const setTodoDueDateAction = (id, dueDate) => ({ type: 'ADD_TODO_TO_LIST', id, dueDate })

// reducer.js
export const setTodoDueDate = (id, dueDate, state) => { /* ... */ }

Then, you create the reducer at the end:

export const reducer = createReducer()

Rather than destructuring, you could group different reducer handlers in the same file if that's your thing. They each maintain their own handlers and ability to create a reducer from it.

const todosHandlers = createHandlers()
const visibilityFilterHandlers = createHandlers()

// ...

export const todos = todosHandlers.createReducer([])
export const visibilityFilter = visibilityFilterHandlers.createReducer('SHOW_ALL')

😕 Drawbacks

Order matters

It requires the order of parameters on the action object to be the same as the parameter list in the handler. This would result in a bug:

// actions.js
const setTodoDueDateAction = (id, dueDate) => ({ type: 'ADD_TODO_TO_LIST', dueDate, id }) // 🐞

// reducer.js
export const setTodoDueDate = (id, dueDate, state) => { /* ... */ }

This bug occurs because there's no way to get parameter names, so we have to do it based on order. We tried just spreading out the action object:

export const setTodoDueDate = ({ id, dueDate }, state) => { /* ... */ } 

... but we found this to be an unpleasant experience for testing and adding types. We accepted it as worth the risk for this minor bug. In practice, it gets uncovered very quickly within the same dev cycle.