Photo by Ben White on Unsplash

Redux without State

WHY?

Kevin Ghadyani
ITNEXT
Published in
6 min readAug 21, 2019

--

What if you could get most of the benefits of Redux and didn’t have to write a single reducer?

What if you had to support a legacy AngularJS, jQuery, Backbone, or Knockout application? If you want to use Redux right now, you’d have to worry about having two sets of state.

Well you can! And all you need is Redux without state.

But Redux is a state manager:

So why would anyone use Redux without state? It’s like someone gave you an already-eaten apple for lunch. What good is that?

The real power of Redux doesn’t come from state management. That’s just a side-effect of the key ingredient: a messaging system.

Messaging in Redux

Messages are referred to as actions in Redux, and they’re typically used to update state.

Middleware functions are another crucial element to Redux, but they don’t update state. These functions only dispatch actions or do side-effects (things like AJAX calls and console logging).

That means they only listen for actions then dispatch other actions:

Redux middleware is pretty amazing. Here are some examples of what you can do with Redux actions once you add middleware:

In the same way you don’t need Redux to use Redux-Observable, you also don’t need state or reducers to use the action system in Redux. To understand Redux messaging, you have to first understand actions.

What‘s an Action?

At the heart of Redux’s messaging system is the action. It’s an object with a type property.

{ type: 'SOME_ACTION' }

That’s it.

type can be anything: string, object, symbol, or even number. Best-practice is using a string in all caps for easier debugging.

Actions are the key to having a stateless Redux.

Stateless Redux

Implementing your own stateless Redux is pretty simple. We’re going to go over a few different ways to create your own.

You can create your own dispatch function like so:

The examples are in ES5 for compatibility reasons.

If action.type exists in stateModifiers, call that function and pass in the action.

Sound familiar?

Redux reducers listen to actions too. The only difference being a reducer doesn’t mutate state, it returns a new one.

But you’re probably using a legacy system and state mutation is the norm. That’s why these state modifier functions allow you to mutate state.

Here’s what our dispatch function would look like in an AngularJS controller:

Every time we mutate state, it happens in the stateModifiers object. Think of this like a reducer function. Since your app already has a method of maintaining state, all you’re doing is wrapping around that code.

Don’t rewrite your AngularJS just to use Redux. Let Redux work with your existing application. You can progressively add dispatch functions to your controllers and update as necessarily. This way, you don’t have to change rendering libraries just to benefit from Redux.

State modifications are no longer littered throughout your codebase. Now they’re all in one place; easy to find, easy to debug.

For the most part, all you need is dispatch. Start console logging actions, and you’re already benefiting from Redux messaging.

Adding Subscribers

Dispatching is great, but most Redux applications need middleware. You need some way to say “when X is changed, dispatch other actions”.

While most libraries like Knockout and Angular allow you to listen for state changes, that’s only part of the equation.

You might want to transform an action into another action or perform a side-effect. One such example is hooking into a 3rd party libraries — think support chat clients and services like LogRocket. A lot of the time, a single action will trigger more than one piece of code to execute.

This is why you need a way of subscribing to your dispatch function and executing middleware after state updates:

Here’s a simple example of why you’d want middleware:

Compared to our earlier example, we no longer make a data call when a click occurs. Instead, we’re only dispatching the GET_IDS action.

In our example, two middleware functions are listening for GET_IDS. One triggers a loading indicator. The other makes the data call.

This is where Redux’s separation-of-concerns shines. Your click handler no longer has to manage your business logic; that’s handled in your state modifier and middleware functions.

Cheating

If you wanted to create middleware using your stateModifiers object, that’s technically possible. Thing is, I don’t recommend it.

If you combine your state modifiers with middleware functions, you lose out on keeping state updates part of a separate entity.

Making it Reusable

Let’s say you wanted to use this in your application today. You’d wanna write it a bit differently. Something like this:

Now we have error logging and everything’s contained in a createStore function just like Redux. The only difference is there’s no state other than the “stores” internal list of subscribers.

In this version subscribe can take an array of middleware, and our dispatch function has some safety checks and error logging.

If you set window.__isDebugging to true, it will also log every action so you can track what’s going on. Sure, this isn’t Redux’s devtools, but it’s still helpful.

If you see a big blog of red error messages, you can now track which action caused it from looking at your console.

Here’s how you’d used it:

While Redux is a “one global store to rule them all” kinda library, this stateless version can, and probably should, be used in each controller.

That way, you have more control over what’s happening without having your entire app globally aware of itself. In a lot of legacy applications, this is what you want.

Using Stateless Redux with Legit Redux

You could also do a stateless Redux with the official library. All you’d have to do is use one reducer and always return the previous state:

Not as good as a custom solution, but then you can hook into existing middleware libraries like Redux-Observable no problem.

Although, if you have the capability of bringing in npm dependencies, you probably aren’t in need of a stateless Redux either.

Real Object-Oriented Programming

I’ve been talking about Redux’s messaging system like it’s something phenomenal, and it is.

Alan Kay, the guy who termed Object-Oriented Programming, was actually talking about Message-Oriented Programming.

From a 2003 email to Alan Kay, this is what he said:

I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages (so messaging came at the very beginning — it took a while to see how to do messaging in a programming language efficiently enough to be useful).

I wanted to get rid of data. […] I realized that the cell/whole-computer metaphor would get rid of data, and that “<-“ would be just another message token (it took me quite a while to think this out because I really thought of all these symbols as names for functions and procedures.)

That’s stateless Redux in a nutshell.

Conclusion

Before Redux, there weren’t many good methods to keep track of your application’s state.

Looking back, a lot of jQuery’s code was also event-driven, just like Redux’s message system. You’re listening to things like click event handlers, mouse move, tracking updates, document loaded, and image loaded. But why is Redux so clean and jQuery turns into spaghetti code?

In the past, you subscribed to a specific action (event). You couldn’t subscribe to any and all actions like with Redux middleware. Also, there’s no separation between state updates and middleware functions when using event emitters.

Think about Redux in terms of cost.

It has a bit of short-term cost, but in the long run, it’s widely maintainable and separates the business logic from your views. Even with a stateless Redux, while you don’t get immutable state and easily-testable pure functions, you gain immense benefits knowing exactly what’s going on.

More Reads

If you liked what you read, please checkout my other articles on similar eye-opening topics:

--

--