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-reflex

v0.1.1

Published

Reduce boilerplate code by automatically creating action creators and action types from reducers.

Downloads

48

Readme

redux-reflex

Reduce boilerplate code by automatically creating action creators and action types from reducers.

Build Status Coverage Status

Installation

npm install redux-reflex --save

Example

reducer.js

import Reducer from 'redux-reflex'

const prefix = 'counter'
const initialState = { count: 0 }
const handlers = {
  // called when action type is 'counter/increment'
  increment(state, amount) {
    return { ...state, count: state.count + amount }
  },
  // called when action type is 'counter/decrement'
  decrement(state, amount) {
    return { ...state, count: state.count - amount }
  },
}

const counter = Reducer(prefix, initialState, handlers)

export default counter

This will create a reducer function counter with initialState as the initial state and handlers as case reducers. handlers handle actions with type starting with prefix.

You don't have to define action creators or action types. They are automatically created and attached to the reducer function:

counter.increment // action creator (function)

counter.increment.type // action type (string) - 'counter/increment'

counter.increment() // creates action (object) - { type: 'counter/increment' }

dispatch(counter.increment(5)) // increases count by 5

Documentation

Generated actions are similar to Flux Standard Action.

counter.increment()
/*
{
  type: 'counter/increment',
}
*/

counter.increment(5)
/*
{
  type: 'counter/increment',
  payload: 5,
}
*/

If payload is an instance of an Error object then error is automatically set to true.

counter.increment(new Error())
/*
{
  type: 'counter/increment',
  payload: new Error(),
  error: true,
}
*/

Reducer

The main Reducer function creates a reducer and corresponding action creators and types.

import Reducer from 'redux-reflex'

const todos = Reducer(...)

Reducer(prefix, initialState, handlers, options = {})

prefix

prefix should be unique as prefix + '/' is used as a prefix for the action types that are automatically created. You can follow this convention to keep them unique: <app-name>/<feature-name>/<reducer-name>.

initialState

Initial value of the state managed by reducer.

handlers

Action handlers let you split the reducer code into smaller functions instead of using switch-case statements. These are also called "case reducers".

handler(state, payload, action)

Each handler should be a pure function that reads current state, payload and action and returns a new state: (state, payload, action) => state. A typical reducer function looks like (state, action) => state but here payload is passed as the second argument as it will be required most of the time. If you need more info about an action, you can always use the third argument action which contains action type, payload and other data related to action. For example, to check if an action is dispatched because of an error:

// some action handler
fetched(state, payload, { error }) {
  // state should not be modified directly
  // so do a shallow copy first
  state = { ...state }

  // state updates common to both success and failure cases
  state.fetching = false

  // failure case
  if (error) {
    state.error = payload
    return state
  }

  // success case
  state.data = payload
  return state
}

Here we used Destructuring assignment to unpack error from the third argument action.

options

options object is used for configuring the Reducer. Currently these options are available:

{
  copy: false,
  // when `true`, `Reducer` automatically does a shallow copy of `state`
  // before calling a handler function
}
import Reducer from 'redux-reflex'

const todos = Reducer(
  'todos',
  { todos: [] },
  {
    // this function will be called when action type is 'todos/add'
    add(state, { text }) {
      // Since `copy` option is set to `true`, `Reducer` automatically
      // does a shallow copy of `state` before calling this function.
      // So you don't have to do `{ ...state }`.

      // `concat()` returns a new copy of array
      // and doesn't modify the original one.
      // `push()` should not be used as it modifies
      // the original array.
      state.todos = state.todos.concat(text)

      return state
    },
  },
  { copy: true }
)

Here we used Destructuring assignment to unpack text from payload. If payload is not passed as second argument then unpacking complex objects becomes hard to read.

Actions

Action creators are automatically created from reducer functions. If you need to define more action creators, you can use the Action function:

import { Action } from 'redux-reflex'

todos.add = Action('todos/add')

todos.add({ text: 'some task' })
/*
{
  type: 'todos/add',
  payload: { text: 'some task' },
}
*/

Action(type, transform = payload => payload)

type

type specifies the action type and should be unique. You can follow this convention to keep it unique: <app-name>/<feature-name>/<reducer-name>/<action-name>.

transform

transform is a function that can be used to modify the payload:

import { Action } from 'redux-reflex'

todos.add = Action('todos/add', payload => ({ text: payload }))

todos.add('some task')
/*
{
  type: 'todos/add',
  payload: { text: 'some task' },
}
*/

transform(actionCreator, transform)

Apply a transform function to existing action creators:

import Reducer, { transform } from 'redux-reflex'

const todos = Reducer(...) // assume that todos.add is already defined here

todos.add('some task')
/*
{
  type: 'todos/add',
  payload: 'some task',
}
*/

todos.add = transform(todos.add, payload => ({ text: payload }))

todos.add('some task')
/*
{
  type: 'todos/add',
  payload: { text: 'some task' },
}
*/

Async Actions

Async actions can be handled using a middleware like redux-thunk:

// define `todos.fetching` and `todos.fetched` using `Reducer` function
// and use the `error` property inside `todos.fetched` handler function
// to handle both success and failure cases
todos.fetch = () => dispatch => {
  dispatch(todos.fetching())
  return api
    .get('/todos')
    .then(response => dispatch(todos.fetched(response.data)))
    .catch(error => dispatch(todos.fetched(error)))
}

// dispatch it like a normal action
dispatch(todos.fetch())

Reusing Reducer Logic

Let's say you want to have two counters both having their own state but with same functionality. To do so, you just have to define two reducers with same initial state and handlers:

import { combineReducers, createStore } from 'redux'
import Reducer from 'redux-reflex'

const initialState = { count: 0 }
const handlers = {
  increment(state, amount) {
    return { ...state, count: state.count + amount }
  },
  decrement(state, amount) {
    return { ...state, count: state.count - amount }
  },
}

const counter1 = Reducer('counter1', initialState, handlers)
const counter2 = Reducer('counter2', initialState, handlers)

const store = createStore(combineReducers({ counter1, counter2 }))

Here counter1 and counter2 have their own slice of state and calling an action of one counter doesn't affect the state of other.

counter1.increment() // handled by counter1 reducer
/*
{
  type: 'counter1/increment',
}
*/

counter2.increment() // handled by counter2 reducer
/*
{
  type: 'counter2/increment',
}
*/

License

MIT