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

v0.3.1

Published

A nice new way to do actions

Downloads

18

Readme

Redux Atomic

"Give Those Typing Hands A Break"

What?

Here is a library for helping you type less by replacing separate actions/reducers in Redux with functions that take a piece of state and return a new one.

Like magic?

Nah, nothing magic is going on, making your reducer this way means we can autocreate typed action creators, thus saving you typing out loads of code that adds little value.

Why?

Actions in languages like Elm and Purescript are generally described by a type such as DoThing that may take a parameter or two like ChangeName('Bruce') or SetCursorPosition(1, 3) - this libraries aims to provide that simplicity.

How?

Check out the example app!

Or alteratively dive right in, and install it from your favourite terminal with npm install redux-atomic

Then type something like this into a text editor or similar:

// niceReducer.ts

import { createReducer } from 'redux-atomic'

const initialState: number = 0;

const inc = (howMuch: number) => (state: number): number => state + howMuch

const dec = (howMuch: number) => (state: number): number => state - howMuch

const { reducer, wrap } = createReducer("niceReducer", initialState, {inc, dec})

export numberReducer = reducer

export actions = {
    inc: wrap(inc),
    dec: wrap(dec),
}

This reducer can then be connected to others however you like to do that...

// store.ts

import { numberReducer } from "niceReducer";
import { someOtherTerribleReducer } from "awfulFile";

const appReducers = combineReducers({
  numbers: numberReducer,
  otherStuff: someOtherTerribleReducer
});

And the actions can be connected to a component something like this...

// connectorComponent.ts

import { actions } from 'niceReducer'

const mapDispatchToProps = (dispatch) => {
    inc: dispatch(actions.inc),
    dec: dispatch(actions.dec)
}

Why though?

I mean, really, why anything?

But sure, why this? Am I supposed to rewrite my whole app now?

Let's look at a code example for a basic reducer with actions in the full-fat Redux style.

const MAKE_THE_NUMBER_BIGGER = "someSortOf.NameSpace.MAKE_THE_NUMBER_BIGGER"
const MAKE_THE_NUMBER_SMALLER = "someSortOf.NameSpace.MAKE_THE_NUMBER_SMALLER"

interface NumberBiggener {
    type: typeof MAKE_THE_NUMBER_BIGGER
    payload: {
        howMuch: number
    }
}

const numberBiggener = (howMuch: number) => ({
    type: MAKE_THE_NUMBER_BIGGER,
    payload: {
        howMuch
    }
})

interface NumberSmallener {
    type: typeof MAKE_THE_NUMBER_SMALLER
    payload: {
        howMuch: number
    }
}

const numberSmallener = (howMuch: number) => ({
    type: MAKE_THE_NUMBER_SMALLER,
    payload: {
        howMuch
    }
})

type NiceState = number

const initialState: NiceState = 0

function numberReducer(state: NiceState, action: NumberBiggener | NumberSmallener ): NiceState {
  switch (action.type) {
      case MAKE_THE_NUMBER_BIGGER:
        return state + action.payload.howMuch

    case MAKE_THE_NUMBER_SMALLER:
        return state - action.payload.howMuch

    default:
        return state;
  }
}

export numberReducer;
export actions = {
    numberBiggener,
    numberSmallener
}

The names are a bit ridiculous, granted, and typings do make stuff more verbose, but that's still at lot of stuff. With a few more actions you'd be irresponsible not to move the action creators and reducer into separate folders, and then all the constants need exporting/importing etc etc. All in all in that's a lot of code that's not describing much of anything.

Surely working in this way ties each action to each reducer?

Yes true. The main actions Redux Atomic creates are designed to make the 90%-of-the-time case of 1-1 action-reducer relationships easier to do. To allow your reducer to pick up global actions, see the listeners parameter for createAtomic() below.

Can I still view these actions in Redux Dev Tools etc?

Yeah sure, the auto generated actions have the format:

{
    type: `reducerName_actionName`,
    payload: ["list", "of", "called", "args"]
}

Therefore in a reducer called niceReducer, with a function like:

const newTitle = (newTitle: string) => (state: State): State => {
  return {
    ...state,
    title: newTitle
  };
};

...called like this:

dispatch(newTitle("hello"));

Would produce an action like

{
    type: 'niceReducer_newTitle',
    payload: ["hello"]
}

This would then be picked up by the newTitle function passed into the reducer.

API reference

createAtomic(reducerName, initialState, reducers, listeners = {})

Parameters:

reducerName is the name of your reducer - it must be unique across the application.

initialState is the starting data state of your reducer.

reducers is how you provide your functionality to Redux Atomic. Pass them in as an array of reducer functions, or an array of objects in this format: {actionName: actionFunction} or indeed just {actionFunction, anotherActionFunction, yetAnother} etc.

listeners is how you respond to actions from outside this reducer, and are passed in the form {[ACTION_NAME_TO_LISTEN_TO]: niceFunction}, where niceFunction is a function of type (state, action) => state

Returns:

reducer - your reducer function to connect with combineReducers etc, that can be passed actions in the regular (state, action) => state type manner.

wrap - function for wrapping your reducer functions and auto-creating actions. See below for usage.

actionTypes - an array of strings with the type of each action that your reducer responds to - mostly provided for debugging purposes, ie

const { actionTypes } = createAtomic("hello", state, { great, job, nice, functions });
// actionTypes == ['hello_great', 'hello_job', 'hello_nice', 'hello_functions']

wrap(reducerFunction, actionName)

Parameters:

reducerFunction is the reducer action you wish to wrap and turn into a dispatchable action. It must take some number of parameters, and returns a function which turns state into new state.

actionName is optional - you will only need to provide it if the function is a const function imported from another file so we cannot work it out automatically. It should match the name you passed to createAtomic, and will throw an error if not.

Returns:

A dispatchable action for your function, which expects the same parameters as your reducer function.

Errors you may see and what they are

Redux Atomic: Error in wrap for niceReducer! niceFunction cannot be found. Did you remember to pass it to 'createAtomic()'?

You have tried to wrap a function that has not been passed to the reducer. Make sure your function is passed to the 'reducers' array in createAtomic()

Redux Atomic: Error in createAtomic for niceReducer! A reducer with this name already exists!

As Redux Atomic creates regular Flux actions under the hood, having two reducers with the same names means they will both pick up some of the same functions and things can quickly get messy and quite confusing. This error will be thrown if you try and do this.

Redux Atomic: Error in wrap for niceReducer! Could not wrap function greatFunction as it has not been passed to createAtomic();;

This is to make sure we don't end up with names not matching between passing to createAtomic() and wrap(). If this occurs - check you've sent the function you intend to wrap and any passed names match.

Redux Atomic: Error in createAtomic for niceReducer! Item 2/4 is not a valid function. Please pass in functions in the form: '{functionName:function,functionName2:function2}'

You have passed something which is not a function to createAtomic().

Anything else?

Building your reducers in this way means you can have multiple instances of them that don't interact with one another, so long as they are given different names and each set of actions are exported with the matching wrap() function.

Testing

As these are just functions, testing them is pretty straightforward. Jest tests for the above look something like this:

const initialState = {
  title: "Yeah",
  number: 100
};

describe("Redux atomic is OK", () => {
  it("Increments like a professional", () => {
    expect(increment(10)(initialState)).toEqual({ title: "Yeah", number: 110 });
  });
  it("Decrements like a professional", () => {
    expect(decrement(10)(initialState)).toEqual({ title: "Yeah", number: 90 });
  });
  it("Renames in an efficient and timely manner", () => {
    expect(rename("Relatable pop culture reference")(initialState)).toEqual({
      title: "Relatable pop culture reference",
      number: 100
    });
  });
});

Testing the outcome of a series of actions is as simple as composing them together (using Ramda's compose here but hopefully you get the idea):

import { compose } from "ramda";

const initialState = { title: "blah", number: 0 };
const bunchOfActions = compose(
  increment(10),
  decrement(20),
  rename("Dog")
);
const expected = { title: "Dog", number: -10 };

expect(bunchOfActions(initialState)).toEqual(expected);

Seems fine? Sure. Most things are.

Thanks

Thanks to the creators and maintainers of Redux, for making a simple sane solution for state management. Thanks Riku and David for knowing facts about these sort of things which I stole and used to make the thing better.