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

easy-ducks

v2.0.1

Published

A utility to simplify the Redux modular duck pattern

Downloads

10

Readme

Easy Ducks

travis build License: MIT

Easy Ducks is a utility that simplifies the implementation of the ducks pattern for async actions in Redux. It eliminates the need to manually create reducers (no more switch statements!) or action types, and greatly simplifies action creators.

Easy Ducks automatically handles loading states by adding loading and didLoad keys to each reducer. When a request is in progress, loading will be true, and when a request has previously completed at some point, didLoad is set to true. This can be useful if you want to show a different loading state for when there is some pre-existing data, versus the initial load with no data.

By default, Easy Ducks adds async responses to the reducer by with object spread notation, e.g.,

(state, action) => ({ ...state, ...action.response })

You can override this by providing resolver functions to action creators in order to define custom behavior.

Easy Ducks assumes you're using redux-thunk middleware in your project.

The default configuration relies on the fetch API, so you may need to provide a polyfill for extended browser support. Alternatively, you can use plugins to support other http implementations.

Installation

yarn add easy-ducks

# or

npm install --save easy-ducks

Quick Start

First create your duck and export the reducer:

// ducks/users.js

import { Duck } from 'easy-ducks'

const duck = new Duck('myDuck', {
  baseUrl: 'https://myapi.com/api'
})

// Pass this to redux's combineReducers function
export default duck.reducer

// Action creators
export const getUser = id => duck.get(`/users/${id}`)

export const createUser = (params = {}) => duck.post('/users', { params })

export const editUser = (id, params = {}) => duck.put(`/users/${id}`, { params })

export const deleteUser = id => duck.delete(`/users/:id`)

Then add the exported duck.reducer to your root reducer:

// ducks/index.js

import { combineReducers } from 'redux'

import users from './users'

export default combineReducers({ users })

Options

Instance Options

The first constructor argument is a string indicating the name of the duck. The name is used to assemble the Redux action type, so it must be unique.

The format for the generated action types looks like this:

[{name}] {method}: {status}

For example, if you create a duck with the name myDuck, the actions for duck.get() would look like this:

// GET begin
{ type: '[myDuck] GET: BEGIN' }

// GET error
{ type: '[myDuck] GET: ERROR', error }

// GET success
{ type: '[myDuck] GET: SUCCESS', response }

The second constructor argument is an instance configuration object:

| Name | Type | Required | Default | Description | |--------------|----------|----------|---------|---------| | baseUrl | string | true | | The base url for your api | | initialState | object | false | | The default value to be passed to the reducer | | plugin | function | false | | Allows for http implementations other than fetch | | storeParams | boolean | false | false | Tells Easy Ducks to save any params passed to a request in the reducer |

As an alternative to storeParams, you can include a params object in the initialState:

const duck = new Duck('myDuck', {
  baseUrl: 'https://myapi.com/api',
  initialState: {
    params: {}
  }
})

Request Options

The first request argument is path, which is a string that indicates the remainder of the request URL after the baseUrl provided in the constructor.

The second request argument is an optional configuration object:

| Name | Type | Required | Description | Arguments | |--------------|----------|----------|-------------|-----------| | actionModifiers | object | false | Allows for modifying the dispatched action. | | | onError | function | false | Callback function for request error | error,  getState | | onSuccess | function | false | Callback function for request success | success,  getState | params | object | false | Contains any parameters to be passed with the request. | | | resolver | function | false | Allows custom handling of responses | state,  action | | | verb | string | false | Specifies an alternate verb to use in the action type. Defaults to the http method, e.g. get, post, etc. | |

Global configuration

This package provides a named export called DuckFactory to simplify re-use of configuration values across all duck instances.

// duckFactory.js

import { DuckFactory } from 'easy-ducks'

import plugin from 'utils/myPlugin'

const duckFactory = new DuckFactory({
  baseUrl: 'https://my-api.com/api',
  plugin
})

export default duckFactory

Now import this instance into your individual duck files and create new ducks from that. Ducks created using this method will inherit any configuration options that you previously specified.

import duckFactory from '../duckFactory'

const duck = duckFactory.create('users')

export default duck.reducer

export const getUsers = () => duck.get('/users')

Action Modifiers

Action modifiers are functions that allow you to modify the object that is passed to the dispatch function. You can provide a modifier for each of the three statuses: begin, success, and error.

| Modifier | Arguments | |-----|-----------| | begin | getState | | success | response,  getState | | error | error,  getState |

This functionality can be useful if you're using some type of redux analytics middleware, such as redux-segment to track events based on redux actions.

In this example, the analytics key would be added to the object passed to dispatch:

const fetchUser = id => duck.get(`/users/${id}`, {
  actionModifiers: {
    success: (response) => ({
      meta: trackEvent('viewed user', { id, name: response.name })
    })
  }
})

Callbacks

The dispatch function returns a promise, so if you want to perform actions after the request's success or failure you can do so inside a .then block.

For example, if you wanted to save a response to local storage on success:

import localStorage from 'store'

store.dispatch(fetchUser(1))
  .then((response) => {
    localStorage.set('user', response)
  })

Sometimes you may want to perform some action inside the action creator itself. For this scenario there are two optional callbacks, onSuccess and onError. These callbacks receive response and error, respectively, as arguments.

Using these callbacks, the example above would look like this:

export const fetchUser = id => duck.get(`/users/${id}`, {
  onSuccess: (response) => {
    localStorage.set('user', response)
  }
})

Resolvers

Resolvers are functions that allow you to define custom behavior for updating the store. Resolvers take the same arguments as the reducer itself, state and action, and return the new state on request success.

For example, if a reducer contains an array of users, and you want to add the new user object returned by your createUser action creator, you can define a resolver that adds it to the end of the array.

export const createUser = (params = {}) => duck.post('/something', {
  params,
  resolver: (state, action) => ({ ...state, users: [...state.users, action.response.user] })
})

Plugins

Easy Ducks uses fetch by default to make http requests, but you can provide plugins if you'd like to use something else. Plugins for axios and fetch are included with the library.

import axiosPlugin from 'easy-ducks/lib/plugins/axios'

const plugin = axiosPlugin()

const duck = new Duck('myDuck', {
  baseUrl: 'https://myapi.com/api',
  plugin
})

You can also provide a configuration object to plugin which will be passed along to the config options for the http solution.

const plugin = axiosPlugin({
  headers: {
    Authentication: 'my-auth-token'
  }
})

Since fetch is used by default, you only need to explicitly provide the fetch plugin if you want to pass it a custom configuration object.

Notes:

  • If you're using the axios plugin, you must have axios installed as a package dependency.
  • If you're using fetch, query params must be included directly in the path string.

Writing Plugins

Plugins are simply functions that take an object and map the values to the http library of your choice. The object keys are:

  • baseUrl - The base URL of your API
  • method - e.g., delete, get, post, put
  • path - The remainder of the request endpoint after baseUrl
  • params - An object containing request data

Take a look at the plugins in the src/plugins directory for some examples.

Example Project

This repo includes an example project that you can run to test out the library in action.

# Install dependencies
yarn

# Start the dev server
yarn start

Then go to localhost:7000 to view the example project.