Non event-driven redux

2,153 reads

What if a core idea of flux was wrong for the front-end?

Photo by NeONBRAND on Unsplash

The first time you heard about Flux it probably confounded you. Looking back, it’s kind of surprising that something so simple could have taken me so many articles and tutorials to grasp. I’m so thankful that Redux is one of the most popular derivatives of Flux, because it really is a very simple expression of the core aspects of Flux — a unidirectional flow of data that involves the process of dispatching an action to change the store to change the view.

The actions are simple objects containing the new data and an identifying type property. — https://facebook.github.io/flux/docs/in-depth-overview.html#content

Facebook gives a specific definition of the action, a plain javascript object with certain properties. Although Facebook never explicitly describes the process where the action changes the store as event-driven architecture, it’s hard to imagine any other design pattern when using a plain javascript object to change the store.

There are a few key patterns that are generally seen with event-driven architecture. Event sourcing and event notifications are two that Martin Fowler has talked about on his blog. Event sourcing is about storing a series of events so that may be used to rebuild a system state. Event notifications is about dispatching actions from a system that is agnostic about whoever consumes those actions. One of the advantage of event notifications is the decoupling it provides. An event is emitted, and any process can consume that event without needing to know who else is consuming the event. I think Flux definitely exhibits event sourcing and event notifications, however it’s the latter quality that is potentially problematic.

One of the issues with event notifications is that it can be difficult to trace the flow of events to event handlers since the flow is implied and not explicit. As soon as an event is emitted (or an action is dispatched) it goes into a sort of blackbox that returns the changed state. In the Redux world there have been attempts to adopt certain standards for making these relationships more explicit (like ducks for example), but the real question is why it is necessary to make this relationship more explicit? It should be an accepted tradeoff for using event notifications, but instead it feels like a tradeoff no one really understands why they had to make in the first place.

I think part of the problem with Flux is that Facebook developed it for its own applications in mind. Facebook has extremely complex applications, and I’m guessing the front-end components of these applications are so complicated that they actually require multiple teams to oversee specific domains. I think Flux makes sense for that situation, but personally, I’ve never been involved in any project that required multiple separated teams on the front-end. I’m not talking about groups of 3 or 4 people working together to complete certain aspects of the front-end, I’m talking about dedicated divisions of 15 of more developers that rarely if ever see any overlapping work.

It’s possible that flux isn’t even ideal for Facebook. We know that they adopted Graphql years before they open sourced the technology, and if they were using Relay or some equivalent library for managing state, then they weren’t really using Flux. Yes, Relay was inspired by Flux, but the process of dispatching actions in Relay is so buried that it’s only visible when using debug tools. Come to think of it, Relay might not even be dispatching actions. From the official relay docs “commitUpdate is analogous to dispatching an action in Flux”, but why is it analogous and not simply dispatching an action?

The real problem with Flux and event notifications is a problem with coupling on the front-end. We hear a lot about microservices and to a lesser extend serverless functions, but there are no equivalent concepts on the front-end. There’s something about the front-end that makes it near impossible to separate into smaller packages. Maybe it’s because the front-end is where everything just comes together. Maybe it’s because the experience of the front-end is supposed to be cohesive throughout the entire application. What ever it is, the front-end is extremely difficult to separate into smaller components, and so when we emit events from one system agnostically, we’re really just emitting the events to ourselves.

Dan Abramov has talked about the limits of Redux and how it’s not for every project. Redux isn’t perfect, but it serves its purpose well. It caught the world by the storm two years ago, and aside from Relay, it’s been the most pervasive state management tool for React. Maybe it’s time for a different approach though. Maybe a non-event-driven approach to Redux.

import {createStore} from 'noredux'

const initialState = 1

const store = createStore(initialState)

const reducer = (state)=>state + 1

store.dispatch(reducer)

console.log(store.getState())

// 2

Not Only Redux (noredux) is a library with a different approach for inducing changes to the state. Instead of dispatching an action, you dispatch a reducer. It’s an explicit approach to state alterations.

Don’t be fooled though, this approach has its own limitations. Achieving separation of concerns in a similar way to traditional Redux’s combineReducers is not easy.

import {scopeReducer} from 'noredux'

const pretodosReducerCreator = () => state => state + 1

const selector = state => state.todos

const setter = (state, result)=>({...state, todos: result})

const todosReducerCreator = scopeReducer(selector, setter, pretodosReducer)

or

import {defaultScopeReducer} from 'noredux'

const pretodosReducerCreator = () => state => state + 1

const todosReducer = defaultScopeReducer('todos', pretodosReducerCreator)

Personally, I think the idea behind the library is more important than the library itself. Make Redux more explicit. For anyone looking to tryout Noredux, you can see an example in the noredux github repo. If you decide to try and use Noredux, there’s not a lot of infrastructure for it at this time, so you won’t be able to use the chrome redux devtools or react-router-redux (But react-redux works fine with noredux).

That being said, I created a redux-noredux package so that you can use Noredux within a redux project, and there’s an example app in the github repo to demonstrate its usage. This is the preferred implementation for Noredux at this time.

import {createStore} from 'redux'

import {noreduxAction, enableNoredux} from 'redux-noredux'

import reducers from './reducers'

const store = createStore(enableNoredux(reducers), {todos: 0})

const noreduxReducer = (state)=>({...state, todos: state.todos + 1})

noreduxReducer.type = 'exampleType'

noreduxReducer.args = []

store.dispatch(noreduxAction(noreduxReducer))

/*

{

type: '@@redux-noredux/exampleType'

args: [],

reducer: noreduxReducer,

noredux: true,

}

*/

There’s a small caveat with this approach if you use combineReducers from redux. CombineReducers doesn’t allow state properties that don’t have a reducer attached to them, so you’ll need to fake it out.

import {combineReducers} from 'redux'

import {fakeReducer} from 'redux-noredux'

import soonToBeDones from './soonToBeDones'

import {initialState as todosInitialState} from './todos'

export default combineReducers({

soonToBeDones,

todos: fakeReducer(todosInitialState)

})

Cheers.

Tags