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

v3.2.0

Published

Redux Tools to make redux easy

Downloads

2,300

Readme

Redux Tools

Redux Tools to make redux easy and reduce boilerplate with useful tools and conventions.

Contents

Getting Started

Please view the example folder to get a quick introduction or continue reading.

Models

The Problem

Does this folder structure look familiar to you?

src/
	actions/
		index.js
		products.js
		user.js
	components/
		App.js
		Header.js
		Product.js
	reducers/
		index.js
		products.js
		user.js
	selectors/
		index.js
		products.js
		user.js
	types/
		index.js
		products.js
		user.js
	index.js
	store.js

This is the basic organization shown in many different redux tutorials and examples. This works great until, one day, it doesn't. It becomes cumbersome to make changes across multiple folders when you're only really updating one thing. This is where models come in.

The Solution

The solution is to place things together by domain rather than type. All fetching, updating, and calculating for a domain lives inside one model. This includes actions, types, reducers, selectors, sagas. So that example app structure using models would look like this:

src/
	components/
		App.js
		Header.js
		Product.js
	models/
		products/
			actions.js
			index.js
			reducer.js
			sagas.js
			selectors.js
		user/
			actions.js
			index.js
			reducer.js
			sagas.js
			selectors.js
		index.js
	index.js
	store.js

Model Conventions

A model can be a folder with separate files for actions, reducer, selectors, etc. Or for simple models just a single file may be all you need (similar to Ducks in many ways). At the end of the day it will be an object with one or several of the following keys (each piece is optional):

  • actions
    • an object where each key is an action creator function
  • reducer
    • a reducer function for a branch of the state object, usually with the same name as the model itself
  • sagas
    • a single saga function that will be forked by the root reducer
  • selectors
    • an object of selector functions related to the model
  • types
    • an object of SNAKE_CASE keys and namespace/SNAKE_CASE values for use in reducers and sagas (and anywhere else you need them)

Next let's look at each piece in a little more detail.

Actions and Types

This is what a typical definition for a type and action looks like:

const SET_FIRST_NAME = 'user/SET_FIRST_NAME';
const setFirstName = (payload) => ({
	payload,
	type: SET_FIRST_NAME,
});

And that gets even more repetitive when you start dealing with asynchronous actions:

const SAVE_REQUEST = 'user/SAVE_REQUEST';
const SAVE_SUCCESS = 'user/SAVE_SUCCESS';
const SAVE_FAILURE = 'user/SAVE_FAILURE';
const saveRequest = (payload) => ({
	payload,
	type: SAVE_REQUEST,
});
const saveSuccess = (payload) => ({
	payload,
	type: SAVE_SUCCESS,
});
const saveFailure = (payload) => ({
	payload,
	type: SAVE_FAILURE,
});

That repetition was a source of great angst for us so we thought there's a great opportunity for convention and tooling to reduce boilerplate.

The first step was to decide that since actions and types are so closely related anyway, why don't we explicitly couple them together?

Conventions for Actions and Types
  • actions have a type and a payload. Action creators will always accept a single parameter that will be placed onto the payload key of the action object.
  • types will be SNAKE_CASE with a namespace of any case
  • action creators will have the camelCase version of the type's SNAKE_CASE value.
  • action creators will be generated automatically from types

So using our conventions and adding in redux-tools tooling, we can greatly reduce the boilerplate for creating actions and types. Here's an equivalent of the above two examples:

import { Async, Basic } from 'redux-tools/types';
import { generateActionsAndTypes } from 'redux-tools';

const { actions, types } = generateActionsAndTypes('user', [
	new Async('SAVE'),
	new Basic('SET_FIRST_NAME'),
	new Basic('SET_LAST_NAME'),
]);

Reducers

Model reducers are combined into the root reducer using Redux's combineReducers function.

Sagas

Model sagas are combined by forking each saga inside the root saga.

API Reference

Types

Use an implementation of the Type class when defining your types. Redux Tools comes with two types defined out of the box: Async and Basic.

Basic

When used with generateActionsAndTypes, this will result in a namespaced type and action creator.

import { Basic } from 'redux-tools/types';
import { generateActionsAndTypes } from 'redux-tools';

const { actions, types } = generateActionsAndTypes('user', [new Basic('SET_FIRST_NAME')]);

console.log(actions);
// { setFirstName: (payload) => ({ payload, type: 'user/SET_FIRST_NAME' }) }

console.log(types);
// { SET_FIRST_NAME: 'user/SET_FIRST_NAME' }

Async

When used with generateActionsAndTypes, this will result in request, success, and failure action creators and types.

import { Async } from 'redux-tools/types';
import { generateActionsAndTypes } from 'redux-tools';

const { actions, types } = generateActionsAndTypes('user', [new Async('SAVE')]);

console.log(actions);
/*
{
	saveRequest: (payload) => ({ payload, type: 'user/SAVE_REQUEST' }),
	saveSuccess: (payload) => ({ payload, type: 'user/SAVE_SUCCESS' }),
	saveFailure: (payload) => ({ payload, type: 'user/SAVE_FAILURE' }),
}
*/

console.log(types);
/*
types: {
	SAVE_REQUEST: 'user/SAVE_REQUEST',
	SAVE_SUCCESS: 'user/SAVE_SUCCESS',
	SAVE_FAILURE: 'user/SAVE_FAILURE',
}
*/

generateActionsAndTypes

Arguments

  1. namespace (string)
  2. types (array)

Returns

{
	actions: {},
	types: {},
}

Example

import { Async, Basic } from 'redux-tools/types';
import { generateActionsAndTypes } from 'redux-tools';

const { actions, types } = generateActionsAndTypes('user', [
	new Async('SAVE'),
	new Basic('SET_FIRST_NAME'),
	new Basic('SET_LAST_NAME'),
]);

console.log(actions);
/*
{
	saveRequest: (payload) => ({ payload, type: 'user/SAVE_REQUEST' }),
	saveSuccess: (payload) => ({ payload, type: 'user/SAVE_SUCCESS' }),
	saveFailure: (payload) => ({ payload, type: 'user/SAVE_FAILURE' }),
	setFirstName: (payload) => ({ payload, type: 'user/SET_FIRST_NAME' }),
	setLastName: (payload) => ({ payload, type: 'user/SET_LAST_NAME' }),
}
*/

console.log(types);
/*
types: {
	SAVE_REQUEST: 'user/SAVE_REQUEST',
	SAVE_SUCCESS: 'user/SAVE_SUCCESS',
	SAVE_FAILURE: 'user/SAVE_FAILURE',
	SET_FIRST_NAME: 'user/SET_FIRST_NAME',
	SET_LAST_NAME: 'user/SET_LAST_NAME',
}
*/

models.create

Function to combine your different models for use in your app. Typically used in the index.js file in your models directory.

Arguments

  1. models (object)

Returns

{
	actions,
	reducer,
	sagas,
	selectors,
	types,
}

Example

import app from './app';
import lists from './lists';
import { models } from 'redux-tools';
import todos from './todos';

export const { actions, reducer, sagas, selectors, types } = models.create({
	app,
	lists,
	todos,
});

console.log(actions);
/*
{
	app: { ... },
	lists: { ... },
	todos: { ... },
}
*/

// Root reducer function to use in your redux store
console.log(reducer);

// Root saga generator function to pass to the saga middleware
console.log(sagas);

console.log(selectors);
/*
{
	app: { ... },
	lists: { ... },
	todos: { ... },
}
*/

console.log(types);
/*
{
	app: { ... },
	lists: { ... },
	todos: { ... },
}
*/

TODO

withErrorReporting: extend to allow raygun / crashalitics / error reporting system withRetry: extend to allow raygun / crashalitics / error reporting system