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

faction

v0.5.2

Published

Utilities for simple, DRY flux actions

Downloads

17

Readme

Faction Build Status

Utilities and conventions for managing Redux (or Flux) actions.

  • Co-locate your action types and action creators and keep them DRY
  • Optionally make an actionCreator function for each action type
  • Automatically dispatch follow-up actions when your async actions complete
  • Validate arguments sent to each actionCreator
  • FSA-compatible

Basic Usage

Note: these examples use ES6/2015 for brevity, but faction will work in any ES5 environment (though you will need Promise & Object.assign polyfills for async action creators).

Define your actions using faction.create(), which transforms your declarative action definitions into action types and action creators:

actions.js

import faction from 'faction'

const actions = faction.create(({ v }) => ({
    ADD_TODO:  { text: v.string },
    EDIT_TODO: { text: v.string },
    MARK_TODO: { isDone: v.boolean.withDefault(true) },
})

export const types = actions.types  // => { ADD_TODO: 'ADD_TODO', EDIT_TODO: 'EDIT_TODO' ... }
export const creators = actions.creators

Each action creator made via faction.create() expects a single object argument:

app.js

import { creators } from './actions'
creators.ADD_TODO({ text: 'hi' })   // => { type: 'ADD_TODO', payload: { text: 'hi' } }
creators.ADD_TODO()                 // => throws an error because of missing arg "text"

Parameter validation

Faction comes with an optional set of utilities for validating the arguments to your action creators. This can be very handy for tracking down bugs in development, similar to how React's propTypes work. Here's a contrived example showing use of all the built-in validators:

import faction from 'faction'

const actions = faction.create(({ v }) => ({
    ADD_TODO:  {
        text:       v.string,
        isDone:     v.boolean,
        priority:   v.number,
        tags:       v.array,
        metadata:   v.object,

        // There are two chainable add-ons that work with all built-in validators:
        category:   v.string.enum(['WORK', 'PERSONAL']),
        reminder:   v.boolean.withDefault(true),
    },
})

By default, these validators throw an error when an input fails validation. This behaviour will soon be configurable (feedback welcome!).

Async action creators using launch()

Faction supports asynchronous action creators using "services". A service is simply a Promise-returning function that you pass in to launch(). Here's a simple example:

import faction from 'faction'

// `launch()` wraps any function that returns a Promise, as shown here using
// the `fetch()` API to get some data from the server:
const fetchTodos = ({ count }) => fetch(`/api/todos?limit=${count}`)

const actions = faction.create(({ launch, v }) => ({
    FETCH_TODOS: launch(fetchTodos, { count: v.number })
}

actions.creators.FETCH_TODOS({ count: 5 })
// => { type: 'FETCH_TODOS',
//      payload: <Promise>,
//      meta: { ... }
//    }

Note that your app's asynchronous logic is isolated in simple functions that can be tested (and mocked if need be) in complete isolation.

Redux middleware for async actions

If you're using redux you can then use faction's built-in middleware to painlessly deal with these Promise action payloads. In the preceding async action example, this middleware will dispatch two separate actions to the store as follows:

First it will dispatch a "pending" action (with the same type), indicating that the asynchronous operation has begun. Note that action.meta.isPending is true:

{ type: 'FETCH_TODOS',
  payload: null,
  meta: { isPending: true, ... }
}

When the Promise resolves, its value will form the payload of the second action that is dispatched from the middleware (again with the same type):

{ type: 'FETCH_TODOS',
  payload: <Promise resolution value>,
  meta: { ... }
}

Also, the service functions that you pass in to launch() will receive the Redux store as a second parameter. This allows you to dispatch extra actions if necessary, or access your state tree in order to create your action. Here's an example of the latter:

const myService = (params, store) => {
    const accessToken = store.getState().accessToken
    return fetch('/myUrl', {
        headers: { Authorization: 'Bearer ' + accessToken }
    })
}

To set up the faction middleware, use faction.makeMiddleware, optionally passing it your action creators if you want to use "chained" actions (explained in the next section):

import { createStore, applyMiddleware } from 'redux'
import faction from 'faction'
import { creators } from './actions'

const createStoreWithMiddleware = applyMiddleware(
    faction.makeMiddleware(creators)
)(createStore)
const store = createStoreWithMiddleware(myReducer)

Chained actions

Sometimes you will want to trigger another action when an async action completes. For example, after a user logs in successfully, you may want to fetch their profile information in a separate request. While you can do this by writing a longer async action creator, it can be preferable to handle this in a more declarative way. Faction lets you append chain(cb) and/or onError(cb) calls to launch() in order to handle these cases:

const actions = faction.create(({ launch, v } => ({
    FETCH_PROFILE: launch(fetchProfile),
    LOGIN_ATTEMPT: launch(loginFn, { username: v.string, password: v.string })
                      .chain((creators, action) => creators.FETCH_PROFILE())
                      .onError((creators, action) => creators.DO_SOMETHING())
})

Note that the return value of the chain() callback must be an action object (or an array of actions), which will then be dispatched (and run through middleware).

Meta fields

Faction autmatically adds some helpful info to each action’s meta object:

  • action.meta.timestamp - a Unix timestamp for when the action was dispatched
  • action.meta.inputs - a copy of the input parameters for the action creator

Running tests

npm test

Note that you'll need Node 4.x or above, since some ES6 features are used in the tests. You can also check code coverage with npm coverage

License

MIT