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-action-schema

v1.0.0-alpha.2

Published

better action management for redux

Downloads

4

Readme

Redux Action Schema

Better action management for Redux

build status npm version

Redux Action Schema is a library for managing actions in Redux apps. It is a replacement for the constants file, providing stronger type guarantees while reducing boilerplate.

Documentation

Examples

Guide

npm install --save redux-action-schema

Creating a schema

In larger Redux projects, action types are frequently collected in a constants file. From the Redux docs:

For larger projects, there are some benefits to defining action types as constants:

  • It helps keep the naming consistent because all action types are gathered in a single place.
  • Sometimes you want to see all existing actions before working on a new feature. It may be that the action you need was already added by somebody on the team, but you didn’t know.
  • The list of action types that were added, removed, and changed in a Pull Request helps everyone on the team keep track of scope and implementation of new features.
  • If you make a typo when importing an action constant, you will get undefined. Redux will immediately throw when dispatching such an action, and you’ll find the mistake sooner.

But the way this is frequently implemented is primitive and repetitive:

export const ADD_TODO = 'ADD_TODO'
export const EDIT_TODO = 'EDIT_TODO'
export const COMPLETE_TODO = 'COMPLETE_TODO'
export const DELETE_TODO = 'DELETE_TODO'
export const COMPLETE_ALL = 'COMPLETE_ALL'
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
export const SET_VISIBILITY = 'SET_VISIBILITY'

export const SHOW_ALL = 'show_all'
export const SHOW_COMPLETED = 'show_completed'
export const SHOW_ACTIVE = 'show_active'

This gets the job done, but its ugly and repetitive. Furthermore it doesn't provide any information about the data in the action, only the type. Redux Action Schema enables compact action definitions with runtime type checks:

const showStates = ["all", "completed", "active"]

const schema = createSchema([
    // match actions with named parameters
    // e.g. { type: "addTodo", payload: { id: 123, text: "here's a todo" } }
    ["addTodo", "here is a docstring",
        ["id", "named params can have docstrings too", types.Number],
        ["text", types.String]],
    ["editTodo",
        ["id", types.Number],
        ["text", types.String]],

    // match actions with single values
    // e.g. { type: "completeTodo", payload: 123 }
    ["completeTodo", types.Number],
    ["deleteTodo", types.Number],

    // match actions with no data
    // e.g. { type: "completeAll" }
    ["completeAll"],
    ["clearCompleted"],

    // match actions with enumerated values
    // e.g. { type: "setVisibility", payload: "completed" }
    ["setVisibility", types.OneOf(showStates)],
])

This provides all of the benefits of using constants, but with additional benefits:

  • Consistent naming: All action types are gathered in the same place. Additionally, the argument names are gathered in the same place, so that those will be consistently named as well.
  • Track changes in pull requests: You can see actions added, removed and changed at a glance in a pull request. Additionally, you can see changes in the action payloads.
  • Handle typos: If you make a typo when using one of the created actions, e.g. schema.actions.compleatTodo, you will get undefined. Additionally, you will get errors if you:
    • use an undefined action creator, e.g. schema.actionCreators.compleatTodo()
    • use an unknown action in createReducer, e.g. schema.createReducer({compleatTodo: (state) => state })
    • dispatch an unknown action when using the validation middleware

Generating a schema in an existing app

Redux Action Schema also includes a middleware that can automatically generate a schema for an existing app. Add the schema observer middleware:

import { createSchemaObserver } from "redux-action-schema"
import { createStore, applyMiddleware } from "redux"

/* ... */

// attached to window so its accessible from inside console
window.schemaObserver = createSchemaObserver()

const store = createStore(reducer, applyMiddleware(window.schemaObserver))

Run the app (manually or with a test runner). Then, from the console:

>   window.schemaObserver.schemaDefinitionString()
<   `createSchema([
        ["foo"],
        ["bar", types.Number],
        ["baz", types.String.optional],
        ["quux", types.OneOfType.optional(types.Number, types.String)],
        ["xyzzy",
            ["a", types.Number],
            ["b", types.String]]
    ])`

You can copy the output of schemaDefinitionString from the console into your code and get a head start on

Generated actions

Protect against typos and automatically handle namespaces with generated actions:

schema.actions.addTodo // => "addTodo"
schema.actions.adTodo  // => undefined

// actions can be namespaced:
const fooSchema = createSchema([...], { namespace: "foo" })
schema.actions.addTodo // => "foo_addTodo"

Action creators

An action creator is generated for each action in the schema:

const { editTodo, completeTodo } = schema.actionCreators
editTodo({ id: 10, text: "write docs" })
// => { type: "editTodo", payload: { id: 10, text: "write docs" } }

completeTodo(20) // => { type: "completeTodo", payload: 20 }

editTodo.byPosition(10, "write GOOD docs")
// => { type: "editTodo", payload: { id: 10, text: "write GOOD docs" } }

Reducers

The schema can be used to create and validate simple reducers a la redux-action's handleActions:

const todoReducer = schema.createReducer({
    addTodo: (state, { id, text }) =>
        state.concat([{ id, text, completed: false }])
    completeTodo: (state, id) =>
        state.map((todo) => todo.id === id
            ? { ...todo, completed: !todo.completed }
            : todo)
}, [])

Unlike handleActions, createReducer verifies that a reducer's handled actions are in the schema:

schema.createReducer({
    nope: (state, payload) => state
}, initState)

// => Uncaught Error: "unknown action: nope"

Middleware

Finally, the schema generates a redux middleware for checking that dispatched actions are in the schema:

const store = createStore(
    reducer,
    applyMiddleware(schema.createMiddleware({
        onError: (action) => console.error(action)
    })))

store.dispatch({ type: "idunno" }) // error
store.dispatch({ type: "completeTodo", payload: "notAnID" }) // error

You may choose to use all these features at once, or mix and match -- you don't need to use the action creators or createReducer to benefit from the middleware, nor vice versa.