DEV Community

Cover image for Introducing Paradux: A Redux Helper For Dynamically Adding Reducers
Antonin J. (they/them)
Antonin J. (they/them)

Posted on

Introducing Paradux: A Redux Helper For Dynamically Adding Reducers

I'm a huge fan of Redux. It's super simple and its simplicity is what won over the hearts of many. On top of that simple architecture, we started to build more complex tools in order to help us do more complex things. From Redux Sagas, to various action creator libraries, and reducer managers.

Paradux is a thin helper with barely any code that gives us some solid foundation to stand on in the increasingly complex world. It's a reducer wrapper that allows a developer to register and rederegister reducers dynamically on the go, in the run time.

Check it out on Github!

A Small Sample

Before I get into the “why”, let's check out an example of what Paradux + Redux looks like:

// bootstrap.js
import { createStore } from 'redux';
import reducers from './reducers';
import Paradux from 'paradux';

export const paradux = new Paradux(reducers); //default reducers
export let store = createStore(paradux.reducerWrapper());
Enter fullscreen mode Exit fullscreen mode

Nothing out of the ordinary happens at this point. It works just as expected. You can even pass in combineReducers and it'll work. The magic comes in when you require that file elsewhere and utilize the exported paradux instance.

// components/myComponent.js
import { paradux } from '../bootstrap';
export default myComponent extends WhateverFrameworkComponent {
  componentDidMount() {
    this.componentReducerUnsubscribe = paradux.register((state, action) => {
      // logic
      return state;
    });
  }
  componentDidUnmount() {
    this.componentReducerUnsubscribe();
  }
}
Enter fullscreen mode Exit fullscreen mode

As soon as that component mounts, the closure reducer is added to paradux and now runs as part of the Redux lifecycle. As soon as it unmounts, it disappears.

Why?

The trivial example above looks like it's just complicating things, right? Well, let's simplify them into the featureset that Paradux actually provides us with:

Self-bootstrapping

Instead of having to import all of the reducers to either a “root reducer or into your application bootstrap file, the reducers can call on the paradux instance and register themselves. This allows us to distribute reducers where they're necessary and make components more portable.

Code-splitting

Right now, code-splitting reducers is hacky and not recommended. Which means that the initial payload of your application includes a ton of logic you might not use. If you split reducers between a “logged in and a “logged out user, you still have to deliver all of them. With webpack and Paradux, you can code-split with ease:

// sample route config file
import paradux from './paradux';

export default {
  component: App,
  childRoutes: [
    {
      path: '/admin',
      getComponent(location, cb) {
        require.ensure('./adminReducers', function (require) {
          var adminReducers = require('./adminReducers');
          paradux.register(adminReducers);
        });
      }
    }
  ]
};
Enter fullscreen mode Exit fullscreen mode

Cleanup friendly

One of my biggest pet peeves with Redux is that once you add that reducer in there, it just keeps running and keeps getting re-run constantly even if it's not getting used. Why keep it if it's logic is no longer pertinent to the app? Paradux allows for deregistering and removing reducers. Paradux, by default, returns a “deregister handler when registering; however, it's also possible to register and deregister reducers by a given namespace. Let's look at this example:

import paradux from './paradux';

// elsewhere we registered it with paradux.register(adminReducers, 'adminReducers');
export function logoutUser() {
  return (dispatch) => {
    return fetch('/api/logout')
      .then((res) => res.toJSON())
      .then(() => {
        paradux.deregisterByNamespace('adminReducers');

        // admin reducers no longer available or run.
        dispatch(userLoggedOut());
      })
      ;
  };
};
Enter fullscreen mode Exit fullscreen mode

After logging a user out, you may not have access to a reducer deregister handler so to be safe, you can use a namespace for the handler that you share across the app. I'm using a simple string but constant a la Redux's action types can work as well.

Why not?

Why NOT to use Paradux? Plenty of reasons as well!

  • there's only one person supporting it
  • there's no tooling built around it
  • no large company has battle-tested this idea
  • it makes state slightly less predictable because reducers can be added/removed at any point

The Future

There are a few things on the roadmap currently including:

  • better safeties against removing a reducer by namespace which multiple clients tried to register
  • on-the-fly middleware with similar APIs (to enable, for example, toggleable debug toolset)

Originally posted on my personal blog.

Top comments (0)