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

@touchtribe/redux-helpers

v2.0.1

Published

Simple helpers to use with redux and react

Downloads

542

Readme

@touchtribe/redux-helpers

An opinionated library to create actions and reducers for Redux, heavily based on redux-act and redux-actions.

While Redux is a great library to work with, the boilerplate code needed for a basic setup is quite verbose and not prone to errors. This library aims to make defining and using actions and reducers less of a hassle.

The actions created by this library are not FSA compliant

Install

# NPM  
npm install @touchtribe/redux-helpers  
# Yarn  
yarn add @touchtribe/redux-helpers  

Content

Usage

import { createStore } from 'redux'
import { createAction, createReducer } from '@touchtribe/redux-helpers'
// default redux actionCreator signature
const increment = function () {
  return {
    type: 'INCREMENT'
  }
}
const subtract = function (amount) {
  return {
    type: 'SUBTRACT',
    amount: amount
  }
}
const decrement = createAction('dec')
const add = createAction('add', (amount) => ({ amount }))

const counterReducer = createReducer('counter', {
  'INCREMENT':
    (state) => state + 1,
  'SUBTRACT':
    (state, { amount }) => state + amount,
  [add]:
    (state, { amount }) => state + amount,
  [decrement]:
    (state) => state - 1,
})

const counterStore = createStore(counterReducer)

Api

createAction(type, identityReducer)

Parameters

  • type (string): the type of the action. Will be used as { type: type } for the resulting action.
  • identityReducer: transforms multiple arguments into an object which will be merged with the resulting action. If nothing is supplied, the first parameter of the action-creator can be an object which will be merged with the action.

Usage

Returns a new action creator. The type will be used as the action-type. If you need to support multiple arguments, you need to specify an identity reducer to merge arguments with the resulting action.

// basic action  
const incrementAction = createAction('increment')
// incrementAction()  
// -> { type: 'increment' }  
// incrementAction({ amount: 10 })  
// -> { type: 'increment', amount: 10 }  

// basic action with identityReducer  
const incrementAction = createAction('increment', (amount) => {
  return { amount: amount }
})
// incrementAction = function(amount) {  
//   return {  
//     type: 'increment',  
//     amount: amount //   }  
// }  
//  
// in short:  
const incrementAction = createAction('increment', (amount) => ({ amount }))
// incrementAction()  
// -> { type: 'increment', amount: undefined }  
// incrementAction(20)  
// -> { type: 'increment', amount: 20 }  

// incrementAction.toString() === 'increment'  
// String(incrementAction) === 'increment'  
// { [incrementAction]: 10 } === { 'increment': 10 }  

// multiple parameters  
const someAction = createAction('some', (amount, howMany) => ({ amount, howMany }))
// someAction(10, 20)  
// -> { type: 'increment', amount: 10, howMany: 20 }  

action creator

Action creators are basically functions that take arguments and return an action in the following format:

{
  type: '<action type>',
  ...identity // returned by the identity-reducer
}  

The actions returned by this library are not FSA compliant

createActionDomain(domain)

Returns a domain-prefixed createAction. Usefull if you need to have multiple actions for the resource/domain/type

Parameters

  • domain (string): The domain of the actionCreatorCreator (...giggity). Will be prefixed to the action-types.
const createUserAction = createActionDomain('user')
const fetchUser = createUserAction('fetch', (userId) => ({ userId }))
const updateUser = createUserAction('update', (userId, userData) => ({ userId, data: userData }))
// fetchUser(10)  
// -> { type: 'user//fetch', userId: 10 }  
// updateUser(10, { name: 'test user' })  
// -> { type: 'user//update', userId: 10, data: { name: 'test user' } }  

createActions(prefix, actionMap)

Returns an array mapping action types to action creators.with multiple actions of type <prefix>/<actionMapKey>.

Parameters

  • prefix (string): Will be prefixed to every action-type
  • actionMap (object): Object which keys are used as action-types and values are used as identityReducers.

Usage

let [
  fetch,
  fetchRequest,
  fetchSuccess,
  fetchFail
] = createActions('fetch', {
  init: (id) => ({ id }),
  request: (id) => ({ id }),
  success: (id, data) => ({ id, data }),
  fail: (id, error) => ({ id, error })
})
// fetch.toString() === 'fetch/init'  
// fetchRequest.toString() === 'fetch/request'  
// fetchSuccess.toString() === 'fetch/success'  
// fetchFail.toString() === 'fetch/fail'  

createActionsDomain(domain)

Returns a domain-prefixed createActions. Usefull if you need to create multiple actions, scoped on a domain.

Parameters

  • domain (string): The domain of the actionsCreatorCreator (... yes). Will be prefixed to the action-types

Usage

const createUserActions = createActionsDomain('user')
const [
  fetchUser,
  fetchUserRequest,
  fetchUserSuccess,
  fetchUserFail
] = createUserActions('fetch', {
  init: (id) => ({ id }),
  request: (id) => ({ id }),
  success: (id, data) => ({ id, data }),
  fail: (id, error) => ({ id, error })
})
// fetchUser.toString() === 'user//fetch/init'  
// fetchUserRequest.toString() === 'user//fetch/request'  
// fetchUserSuccess.toString() === 'user//fetch/success'  
// fetchUserFail.toString() === 'user//fetch/fail'  

createReducer(name, handlers, defaultState)

Parameters

  • name (string): The name of the reducer. Can later be used in your selectors or combineReducers as reducer.toString().
  • handlers (object): A map of actions and their reduce functions.
  • defaultState (any): The initial state of the reducer.

Usage

let counterReducer = createReducer(
  'counter',
  {
    'inc': (state) => state + 1
    'add': (state, action) => state + action.amount
  },
  0
)
// counterReducer.toString() === 'counter'  
// String(counterReducer) === 'counter'  
// ...  
const rootReducer = combineReducers({
  [counterReducer]: counterReducer
})
// creates a rootReducer with `counter` as a key in the store.  
// ...  
const getCounterState = (state) => state[String(counterReducer)]
// creates a selector will will return `state.counter`  

By giving the router a name, and re-using the reducer itself as the key, the application will become agnostic of the actual key in the store.

combineActions (...actionTypes)

This allows you to reduce multiple distinct actions with the same reducer.

const incrementAction = createAction('inc')
const decrementAction = createAction('dec')
const counterReducer = createReducer(
  'counter',
  {
    [combineActions('INCREMENT', incrementAction)]:
      (state) => state + 1,
    [combineActions('DECREMENT', decrementAction)]:
      (state) => state - 1,
  },
  0
)

Resolvable Async Actions

Within redux, when needing to wait for the resolution of an Action, generally redux-thunk is used, which you can use for control-flow. If you use redux-saga however, it becomes harder to act based on the outcome of a dispatched action.

This library enables you to dispatch an action and wait for the outcome, while it is being handled by a middleware.

// actions.js
export const [
  fetchUser,
  fetchUserSuccess,
  fetchUserFail
] = createResolveActions({
  init: (userId) => ({ userId }),
  resolve: (userId, user) => ({ userId, user }),
  reject: (userId, error) => ({ userId, error }),
})

// sagas.js
function * userSaga () {
  yield takeEvery(fetchUser, function * ({ resolve, reject, userId, ...action }) {
    try {
      const user = yield call(fetch, `/users/${userId}`)
      yield put(resolve(userid, user)) // put(
    } catch (error) {
      yield put(reject(userId, error))
    }
  })
}

// component.js
// store is a prop as an example implementation of redux.
function UserComponent ({ store }) {
  const [isLoading, setLoading] = useState(true)
  const [user, setUser] = useState()
  const [error, setError] = useState()
  useEffect(() => {
    setLoading(true)
    setError()
    store.dispatch(fetchUser(10))
      .then(resolveAction => setUser(resolveAction.user))
      .catch(rejectAction => setError(rejectAction.error))
      .then(() => setLoading(false))
  }, [])
  
  if (error) {
    return <div>Error: {String(error)}</div>
  }
  if (isLoading) {
    return <div>Loading...</div>
  }
  return <div>{user.name}</div>
}

// reducer.js
function reducer (state, action) {
  switch (action.type) {
    case String(fetchUserSuccess):
      return {
        ...state,
        user: action.user
      }
    case String(fetchUserFail):
      return {
        ...state,
        error: action.error
      }
    default:
      return state
  }
}

resolvableMiddleware

To enable the middleware that makes actions resolvable, it has to be added to the store using applyMiddleware.

When applied, store.dispatch(asyncInitAction) return a Promise while dispatching the action with 2 extra attributes: resolve and reject.

  • action.resolve(...args) will dispatch asyncResolveAction(...args) and then resolve the Promise with that action
  • action.reject(...args) will dispatch asyncRejectAction(...args) and then reject the Promise with that action

When not applied, store.dispatch(asyncInitAction) will just be handled as if it were a normal action.

Usage

import { createStore, applyMiddleware } from 'redux'
import { resolvableMiddleware } from '@touchtribe/redux-helpers'
import rootReducer from './reducers'

const store = createStore(
  rootReducer,
  applyMiddleware(resolvableMiddleware)
)

createResolvableActions

Works like createActions, but has slightly different signature.

  • createResolvableActions(type)
  • createResolvableActions(type, initIdentityReducer)
  • createResolvableActions(type, actionMap)

The result is equal to:

const [
  action,
  actionSucces,
  actionFail
] = createActions('type', {
  init: (payload) => ({ payload }),
  resolve: (payload) => ({ payload }),
  reject: (error) => ({ error })
})
createResolvableActions(type)

Parameters

  • type (string): Will be prefixed to every action-type
  • actionMap (object): Object which keys are used as action-types and values are used as identityReducers.

Returns

[
  initAction, // function(payload) => { type: `${type}/init`, payload, resolve: resolveAction, reject: rejectAction },
  resolveAction, // function(payload) => { type: `${type}/resolve`, payload }
  rejectAction // function(error) => { type: `${type}/resolve`, error }
]

Usage

let {  
 fetch,
 fetchSuccess, 
 fetchFail
} = createResolvableActions('fetch')  
// fetch.toString() === 'fetch/init'  
// fetchSuccess.toString() === 'fetch/resolve'  
// fetchFail.toString() === 'fetch/reject'  
createResolvableActions(type, initIdentityReducer)

Parameters

  • type (string): Will be prefixed to every action-type
  • initIdentityReducer (function): transforms multiple arguments into an object which will be merged with the resulting action.

Returns:

[
  initAction, // function(...args) => { type: `${type}/init`, ... initIdentityReducer(...args) },
  resolveAction, // function(payload) => { type: `${type}/resolve`, payload }
  rejectAction // function(error) => { type: `${type}/resolve`, error }
]

Usage

let {
  fetch,
  fetchSuccess,
  fetchFail
} = createResolvableActions('fetch', (userId) => ({ userId }))
// fetch.toString() === 'fetch/init'
// fetch(10) => { type: 'fetch/init', userId: 10 }  
// fetchSuccess.toString() === 'fetch/resolve'  
// fetchFail.toString() === 'fetch/reject'
createResolvableActions(type, actionMap)

Parameters

  • type (string): Will be prefixed to every action-type
  • actionMap (object): Object which keys are used as action-types and values are used as identityReducers.

Returns

[
  initAction, // function(...args) => { type: `${type}/init`, ...actionMap.init(...args) },
  resolveAction, // function(...args) => { type: `${type}/resolve`, ...actionMap.resolve(...args) }
  rejectAction // function(...args) => { type: `${type}/resolve`, ...actionMap.reject(...args) }
]

Usage

let {
  fetch,
  fetchSuccess,
  fetchFail
} = createResolvableActions('fetch', {
  init: (userId) => ({ userId }),
  resolve: (userId, user) => ({ userId, user }),
  reject: (userId, error) => ({ userId, error })
})
// fetch.toString() === 'fetch/init'  
// fetch(10) => { type: 'fetch/init', userId: 10 }
// fetchSuccess.toString() === 'fetch/resolve'
// fetchSuccess(10, {}) => { type: 'fetch/init', userId: 10, user: {} }  
// fetchFail.toString() === 'fetch/reject'  
// fetchFail(10, 'oh no') => { type: 'fetch/init', userId: 10, error: 'oh no' }

createResolvableActionsDomain(domain)

Works like createActionsDomain. Returns a domain-prefixed createResolvableActions. Usefull if you need to create multiple actions, scoped on a domain.

Parameters

  • domain (string): Will be prefixed to every action type

Usage

const createResolvableUserActions = createResolvableActionsDomain('users')

const [
  fetchUsers,
  fetchUsersSucces,
  fetchUsersFail
] = createResolvableUserActions('fetch')
// fetchUsers.toString() === 'users//fetch/init'  
// fetchUsersSuccess.toString() === 'users//fetch/resolve'  
// fetchUsersFail.toString() === 'users//fetch/reject'