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

stage-director

v0.0.10

Published

Simplified, sane, reducer/action/action-creator definition system for redux

Downloads

22

Readme

Stage Director

Stage Director is a tiny library (~100 lines of code) that simplifies redux development.

¡¡¡BETA NOTICE!!!

Stage Director is currently in beta with a planned release in 2019. We are currently using Stage Director in two projects, but there are other use cases that we have not explored (or debugged) yet. Please report any issues you might find, submit pull requests, and test thoroughly in development before using in any production environment.

During the beta period the API should be considered unstable as it is under active development. If you depend on Stage Director in production during that time, you should install a specific version with:

npm install --save stage-director@<VERSION>

Motivation

Redux is a great way of managing application state, but for many common use cases it requires a lot of plumbing: actions, action creators, and reducers, spread across multiple files, usually in multiple directories.

Stage Director was created by @airshanemode and @jfyles for Any One Thing to provide a streamlined API for working with redux. It reduces the complexity of managing application state and asynchronously interfacing with external APIs so that you can focus on the logic that makes your application amazing.

Installation

npm install --save stage-director

Usage

Stage Director introduces the notion of "directors". These are files that define reducers, action creators, and actions all in the same place.

A simple example

Let's start with a simple login / logout example. In traditional redux, you might have an actions file that contains the actions and action creators that are required to trigger the reducer that will manage whether a user is logged in or logged out. It might look something like this.

export const ACTION_TYPES = {
  LOGIN: 'LOGIN',
  LOGOUT: 'LOGOUT'
};

const actionCreators = {
  login: (username) => (
    {
      type: ACTION_TYPES.LOGIN,
      username
    }
  ),
  logout: () => (
    {
      type: ACTION_TYPES.LOGOUT
    }
  ),
};

export const ACTION_CREATORS = actionCreators;

This contains a bunch of redundant boilerplate code, including replicating the name of each action four times.

Additionally, you'd have a reducer file that checks for certain actions, and modifies the state accordingly. That file might look something like this.

import { ACTION_TYPES } from '../actions/index';

const account = (state = {}, action) => {
  switch (action.type) {
    case ACTION_TYPES.LOGIN:
      return {
        ...state,
        loggedIn: true,
        username: action.username
      };
    case ACTION_TYPES.LOGOUT:
      return {
        ...state,
        loggedIn: false,
        username: null
      };
    default:
      return state;
  }
};

export default account;

With Stage Director, we can reduce all of this logic into a single file that's smaller and easier to read than the aforementioned reducer alone.

import StageDirector from "stage-director";

const account = new StageDirector("account", {
  login: (state, { username }) => ({
    ...state,
    loggedIn: true,
    username
  }),
  logout: (state) => ({
    ...state,
    loggedIn: false,
    username: null
  })
});

export default account;

At first glance, this looks a lot like a set of reducer definitions, and that's because in this case it is. Reducers are the heart of redux - they define how an action changes one state to another. In this case, all we really care about are the reducers, the rest - actions and action creators - can be inferred from the structure of the director. This director sees that we've just defined reducers, so it infers that we want standard actions and action creators to go with them, and it goes ahead and defines them for us.

Let's look through what it gives us:

First of all, account.reducer gives us a reducer that knows how to handle actions with type === "account:login" and type === "account:logout". We can register this reducer directly with a store with something like:

import { createStore } from "redux"

const store = createStore(account.reducer);

Or, as is likely, if we have a number of directors in our project, we can combine them with StageDirector.combine() - the rough equivalent of redux's combineReducers() - and pass the combined reducer to createStore().

In this simple example we get our actions and action creators for free:

account.actions.login({ username: "bob" }) yields { type: "account:login", username: "bob" }, and calling store.dispatch() with that will invoke the correct reducer.

account.actions.logout() yields { type: "account:logout" }.

Notice that the action types are namespaced by director, in this case "account". This means that we can safely use whatever action names make sense in the context of a given director without having to manually prepend things to common action names like "get" or "sync".

Explicit creators

There are some cases where you'll want to do more in your action creator than simply create an action with the appropriate type. For example, you may want to extract a few fields from an object and expose those in the action. Stage Director lets you do this easily:

import StageDirector from "stage-director";

const account = new StageDirector("account", {
  login: {
    create: (user) => ({
      friendCount: user.friends.length,
      username: user.username
    }),
    reduce: (state, { friendCount, username }) => ({
      ...state,
      loggedIn: true,
      username,
      friendCount
    })
  },
  logout: (state) => ({
    ...state,
    loggedIn: false,
    username: null
  })
});

In this example, we've broken the login definition into two parts:

  1. An action creator that takes a user object, computes the number of friends it has and it's username and exposes them in an action
  2. A reducer that takes those fields and the current state and uses them to produce a new state.

While this isn't always necessary, Stage Director provides the flexibility to do it when called for, or to let the library handle it when you just need something standard. As before, we can access the reducer through account.reducer and the actions through account.actions.login() and account.actions.logout(). Invoking this more complex login action, in this case by passing the entire user object, could be as simple as account.actions.login(user). The director would pull the required fields as part of the action creator, and only expose friendCount and username to the reducer.

Asynchronous actions

Stage Director has support for redux-thunk style asynchronous actions out of the box, massively simplifying the development workflow of async code.

Here's a simple async example:

import StageDirector from "stage-director";

const account = new StageDirector("account", {

  login: {
    async: (done, { username, password }) => ({
      fetch("/account/login", {
        method: "POST",
        body: JSON.stringify({ username, password })
      })
      .then(data => {
        if(data.authorized) {
          done.authorized({ username });
          return;
        }
        done.unauthorized();
      })
      .catch(error => {
        done.error(error);
      });
    }),
    reduce: {
      authorized: (state, { username }) => ({
        ...state,
        loggedIn: true,
        username
      }),
      unauthorized: (state) => ({
        ...state,
        unauthorizedAttempt: true
      }),
      error: (state, error) => ({
        ...state,
        error
      })
    }
  },

  logout: (state) => ({
    ...state,
    loggedIn: false,
    username: null
  })

});

In this example we've made the login action asynchronous. It makes a POST request to a backend endpoint with the supplied username and password parameters and, depending on the response, dispatches one of three actions: "account:login:authorized", "account:login:unauthorized", or "account:login:error", depending on the response that comes back, each of which has a corresponding reducer.

Notice that all of those additional response handling actions are created and namespaced automatically and properly, they're exposed to the async action through the done callback object, and all of the inversion of control and dispatching is handled automatically. No more trying to wrap your head around functions that return functions that take dispatch and then use it in their async callbacks to dispatch actions that you hand-roll for each response. Instead, just worry about your business logic in a clean, readible async flow.

As always, the login action creator is available through account.actions.login({ username: "bob", password: "secret" }).

The only additional step required to use async actions like this is applying the redux-thunk middleware when you create your store:

import { applyMiddleware, createStore } from "redux";
import thunk from "redux-thunk";

const store = createStore(account.reducer, {}, applyMiddleware(thunk));

Closing remarks

Not only is hand-coding lots of boilerplate time consuming, it's also error prone. Action types can be typo'd, naming collisions can occur, and the mental strain of following what often amounts to one logical function across many files can lead to misunderstandings and bugs. This problem is even worse in the case of asynchronous redux code since asynchronous action usually has at least two followup actions - success and error - and often more. This causes the amount of code for a simple ajax call to balloon rapidly.

With Stage Director, developers can focus on core business logic instead of writing boilerplate.