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

redan

v0.2.0

Published

A tiny library to make redux more manageable

Downloads

5

Readme

Redan

Redan is an opinionated set of utility methods for Redux. The library has two aims:

  1. Enforce a standardized way of using Redux and Redux Thunk
  2. Reduce the boilerplate required to use Redux

It's based on the work of Redux Act but with less magic and an extra focus on simplifying thunks.

It's called Redan because it favours the way I (Dan) personally like to use Redux.

Install

yarn add redan

Usage

Redan exports three utility functions.

You can import them like so:

import { createAction, createErrorAction, createThunk } from 'redan';

Actions without action types

The basic idea is that using string constants to identify action types is messy. Here's an example of how action creators are normally used in Redux:

action-types.js

export const ADD_TODO = 'ADD_TODO';
export const COMPLETE_TODO = 'COMPLETE_TODO'

actions.js

import * as types from './action-types';

export const createAction(types.ADD_TODO);
export const createAction(types.COMPLETE_TODO);

reducer.js

import * as types from './action-types';

export default (state, action) => {
  switch (action.type) {
    case types.ADD_TODO:
      ...
    case types.COMPLETE_TODO:
      ...
  }
};

The problem is that having action type constants that are separate from the actions is causing indirection. If you want to find anywhere that the addTodo action is being used, you have to search for all usages of both addTodo and ADD_TODO in your codebase, because your reducer identifies the action using the string constant and your components will dispatch the action using the action creator.

But what if we make the action type accessible from the action creator?

const addTodo = createAction('ADD_TODO');

addTodo.type; // => 'ADD_TODO'

Now our code looks like this:

actions.js

import * as types from './action-types';

export const createAction('ADD_TODO');
export const createAction('COMPLETE_TODO');

reducer.js

import * as actions from './actions';

export default (state, action) => {
  switch (action.type) {
    case actions.addTodo.type:
      ...
    case actions.completeTodo.type:
      ...
  }
};

Now, by searching for 'addTodo' you can find all usages of the action. happy days 👍

API

createAction

Creates simple Redux action creators. It accepts a string as an action type and returns an action creator with the action type bound to it.

The action creator accepts a single payload argument which can be anything.

const addTodo = createAction('ADD_TODO');

addTodo('Buy milk'); // => { type: 'ADD_TODO', payload: 'Buy milk' }

/*
 * The action type is bound to the action creator
 */
addTodo.type; // => 'ADD_TODO'

createErrorAction

Creates error action creators. It accepts a string as an action type and returns an error action creator with the action type bound to it.

The action creator accepts error and payload arguments.

const todoError = createErrorAction('TODO_ERROR');

todoError(new Error('invalid todo'), 'Dont buy milk'); // => { type: 'ADD_TODO', error: Error, payload: 'Dont buy milk' }

/*
 * The action type is bound to the action creator
 */
todoError.type; // => 'ADD_TODO'

createThunk

Creates a thunk with start, end and error actions attached to it.

When creating a thunk, there are often 3 useful related actions to dispatch within the thunk:

  • start - dispatched at the start of the thunk, often used to add loading state to the UI
  • end - at the end of the thunk when everything has gone well. Often used to pass the result of some async call to the reducer.
  • error - when some error was thrown, perhaps your API returns a 404. Used to tell the user that something went wrong

The createThunk utility accepts an action type and a callback. It creates these 3 useful actions and dispatches them for you. It also binds the 3 action types to itself so you can reference them in the reducer.

const fetchTodos = createThunk(
  'FETCH_TODOS', 
  user_id => async (dispatch, getState) => {
    const response = await fetch('www.todos.com/todos');
    return response.json();
  }
);

/*
 * The start, end and error types are bound to the thunk
 */
fetchTodos.start.type // => 'FETCH_TODOS_START'
fetchTodos.end.type // => 'FETCH_TODOS_END'
fetchTodos.error.type // => 'FETCH_TODOS_ERROR'

The callback is passed dispatch and getState just like a regular thunk in case you want to do anything more fancy and dispatch extra actions within your callback.

When fetchTodos is dispatched with redux-thunk the following happens:

The start action

The start action is dispatched. The payload is whatever payload is passed to the thunk.

dispatch(fetchTodos(123));

// dispatches { type: 'FETCH_TODOS_START', payload: 123 }

The end action

The end action is dispatched once the callback has completed. The callback can be asynchronous if you like. The payload of the end action is whatever is returned from the callback.

const fetchTodos = createThunk('FETCH_TODOS', user_id => () => {
  return 'hello';
});

// dispatches { type: 'FETCH_TODOS_END', payload: 'hello' }

The end action is NOT dispatched if the callback throws an error.

The error action

If the callback throws, the error action gets dispatched instead of the end action. The error action is passed the error and whatever payload was passed to the thunk.

const fetchTodos = createThunk('FETCH_TODOS', user_id => () => {
  throw new Error('oh no');
});

dispatch(fetchTodos(123));

// dispatches { type: 'FETCH_TODOS_ERROR', error: Error, payload: 123 }

Example usage

actions.js

import { createAction, createThunk } from 'redan';

export const addTodo = createAction('ADD_TODO');
export const completeTodo = createAction('COMPLETE_TODO');

export const fetchTodos = createThunk('FETCH_TODOS', user_id => () => {
  const response = fetch('www.todos.com/todos');
  return response.json();
});

reducer.js

import * as actions from './actions';

export default (state, action) => {
  const { type, payload, error } = action;

  switch (type) {
    case actions.addTodo.type:
      return {
        ...state,
        todos: [ ...state.todos, payload ]
      };
    case actions.completeTodo.type:
      return {
        ...state,
        completed: [ ...state.completed, payload ]
      };
    case fetchTodos.start.type:
      return {
        ...state,
        isLoadingTodos: true,
        error: null
      };
    case fetchTodos.end.type:
      return {
        ...state,
        isLoadingTodos: false,
        todos: payload
      };
    case fetchTodos.error.type:
      return {
        ...state,
        isLoadingTodos: false,
        error: error.message
      };
    default:
      return state;
  }
}