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 🙏

© 2025 – Pkg Stats / Ryan Hefner

reducerless-redux

v1.1.1

Published

redux without reducers

Readme

Reducerless Redux

Redux middleware for intuitively mapping urls to state

Installation

npm install --save reducerless-redux

This assumes that you’re using npm package manager with a module bundler like Webpack or Browserify to consume CommonJS modules.

The following ES6 functions are required:

Check the compatibility tables (Object.assign, Promise, fetch) to make sure all browsers and platforms you need to support have these, and include polyfills as necessary.

Motivation

This project was inspired by React-Refetch. I like the intuitive way that react-refetch maps url's to props, but I found myself wanting at the same time to leverage redux and react-redux connect's easy way of spreading data around nested heirarchies of components. The thing that turns me off about redux, however, is the proliferation of reducers. They always felt cumbersome, especially since the vast majority of the time all I want to is modify a particular piece of state. To that end I've created a redux middleware and store enhancer that maps url's to state, using a single "invisible" reducer. I also like the PromiseState that react-refetch uses, so all actions result in PromiseStates. The state to props mapping can be done by react-redux connect, which already does a great job at that. I thought about wrapping connect with something that would give you automatic fetching and refetching, but decided against it because the same thing can be achieved with recompose in a more explicit way, without that much more code.

Example

Imagine we have a list of foos we want to display. Each foo has details, which we want to display next to the list for the selected foo.

// index.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Provider } form 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import { reducerlessMiddleware, reducerlessEnhancer } from 'reducerless-redux';
import im from 'object-path-immutable';
import App from './my-app';

const store = createStore(
    null,
    null,
    compose(
        applyMiddleware(
        reducerlessMiddleware({
            setKey: (state, key, value) => im.set(state, key, value),
            getKey: (state, key, value) => im.get(state, key),
            getOpts: opts => {
                opts.headers = { 'X-Api-Token': 'token' };
                return opts;
            },
        })
        ),
        reducerlessEnhancer(),
    ),
);

render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
);
// App.js - use lifecycle methods
import React, { Component } from 'react';
import { connect } from 'react-redux';

class FoosPage extends Component {
    componentDidMount() {
        this.props.getFoos();
    }
    componentWillReceiveProps(nextProps) {
        if (this.props.fooId !== nextProps.fooId ) {
            this.props.getFoo(id);
        }
    }
    render() {
        const { foos } = this.props;
        if (foos.pending) {
            return <Loading />
        } else if (foos.rejected) {
            return <Error .../>
        } else {
            return (
                <div>
                    <FoosList foos={foos.value} />
                    <FooDetail />
                </div>
            );
        }
    }
}
export default connect( (state, props) => {
    foos: 'foos',
    selFoo: 'selectedFoo'
},
{
    getFoos: () => ({
        url: '/api/foos',
        key: 'foos',
    }),
    getFoo: (id) => ({
        url: `/api/foos/${id}`,
        key: 'selectedFoo'
    })
})(FoosPage);
// App.js - use recompose to automatically fetch and update
import { compose, lifecycle } from 'recompose';

const FoosPage = ({ foos }) => {
    if (foos.pending) {
        return <Loading />
    } else if (foos.rejected) {
        return <Error .../>
    } else {
        return (
            <div>
                <FoosList foos={foos.value} />
                <FooDetail ... />
            </div>
        );
    }
}

export default compose(
    connect(
        // same as above
    ),
    lifecycle({
        componentDidMount() {
            this.props.getFoos();
        }
        componentWillReceiveProps(nextProps) {
            if (this.props.fooId !== nextProps.fooId ) {
                this.props.getFoo(id);
            }
        }
    }),
)(FoosPage)

Middleware options

{
    // how do we map a key to state? This gives you flexibilty in what you set the 'key' property
    // to in your actions (See action api below). Any function that can immutably update state 
    // can work here, for example: updeep, dot-prop, or object-path-immutable
    // This is optional, but the key can only be a single identifier if this isn't defined.
    setKey: function (state, key, value) { }

    // how do we get state from a key?
    getKey: function (state, key, value) => { }

    // modify fetch options for all requests. Return the modified options.
    // Usefull for setting auth headers, etc.
    getOpts: function (opts) { }
}

Action API

{
    // the url for the request. If this url is pending, it will not be fetched again.
    url: '/api/foos'

    // the key of our state in which to put the result of the http request.
    // The result will be stored inside of a PromiseState. See react-refetch for
    // more documentation about PromiseStates.
    // Will be used by the 'setKey' function (see above) defined in the middleware
    key: ['path', 'to' , 'prop']

    // synchronously update state however you want.
    // Meant to be used on its own without url or key.
    update: function (state) => im.set(state, ['hello'], 'kitty')

    // transform the result of a request. Return the transformed result
    transform: data => {
        data.myprop = 'hello';
        return data;
    }

    // http method
    method: 'GET' | 'POST' | 'PUT' | 'DELETE' // etc.

    // body of request
    body: { ... }

    // do other stuff when the request is fulfilled
    onFulfilled: (data, dispatch) => {
        // update a piece of state with the result
        dispatch({
            update: state => im.set(state, 'selectedFoo', data);
        })
        // or re-fetch a piece of state
        dispatch({
            url: `/api/foo/${data.id}`,
            key: 'foo'
        });
    }

    // override how the response itself is handled. Default is response.json()
    handleResponse: response => response.text()

    // poll the url at the given interval.
    // Any actions that specify the same url as this one will not be fetched
    // while this action is in the refresh queue
    refreshInterval: 5000 // every 5 seconds

    // clear all polling actions
    clearRefresh: true

    // retry an action with the given backoff
    maxRetry: 3 // retry 3 times, then dispatch the error from the last failed attempt
    retryBackoff: 1000 // wait for this long before trying again: attempt number * 1000

    // cache an key-url combination for the specified ammount of time.
    cacheFor: 1000 * 60 * 10 // don't fetch the action url if already fetched within 10 mins     
    
}