back

by tobr·7y ago·view on hn ↗
I've played around recently with a Redux inspired frontend architecture where the complex, nested reducers are replaced with a single one that simply adds every action to an immutable linked list (i.e, `(state, action) => ({state, action})`. This means all the business logic that would normally live in reducers can be moved over to memoized selectors, which fixes the awkward difference in how reducers and selectors are composed that tend to happen in a typical Redux app, and gets all the declarative benefits of selectors. It makes it easy to load code for new selectors asynchronously (for example when navigating to a new route), and because the state is a long list of every state your app has ever been in, they can "rewind" as far back as they need to sync up correctly with the rest of the app.

What I've realised recently is, this is basically event sourcing. Replace actions with events, selectors with projections, and memoization with snapshots. So this submission is certainly timely and of interest to me. Thanks!

5 comments
I wrote a similar comment before, viewing redux as a realization of the event sourcing pattern: https://news.ycombinator.com/item?id=17061827

You can go even further and use the exact same "events" or "actions" for event sourcing on the server side too. You just need to save the list of actions to the server. This lets you do realtime sync and multiplayer really easily!

This.

It makes Implementing CQRS in a single service really easy. GETs load the event log, replay and display the results while POSTs send a command that gets translated into an event. If it is valid it gets appended to the log. This approach is storage agnostic: It doesn’t matter how the log ultimately gets stored

Yeah good connection there. The Redux chrome plugin does indeed show a log of actions and you can rewind through time.

That's another nice way of doing things that way, better diagnostics eh?

If actions are fully deterministic, a list of actions is isomorphic to a list of events: you can always determine what happened by looking at what was requested.

A list of all past states avoids the determinism problem, but the consequence of each action is only implicit in the difference between two states.

For event sourcing, "the state" is the list of events, and what you're calling "the state" is just another selector/projection.

No, I’m suggesting that your state should be a list of event/action objects. By using a linked list, every new state can have a reference back to the previous state. This is not a projection, but a way to immutably append new actions to a list. This list is the state that you’d pass to selectors.
This mirrors exactly the way I've been approaching redux recently as well following the same realization. I feel like this is much cleaner and easier to reason about.
Hi, I'm a Redux maintainer.

Got any examples of this?

Also, how do you feel it's better than a typical Redux app setup?

For me it’s been experimental so far. I’ve not actually used Redux in these experiments, but I’m inspired by where Redux and Re-reselect has been leading me for the past two years or so. The pattern is a gold mine of interesting implications that I’m still discovering, such as real time collaboration, and speculative precomputation/prefetching.

As it moves all the focus from reducers to selectors, and has quite different requirements for how to do caching there, it’s led me to work on a custom selector library with quite different API from reselect/re-reselect. I’d like to release it when my experiments start to feel... less experimental.

Wow, I'm super curious about this now. Please keep us posted
That’s nice to hear, good motivation to get it out the door. It might take some time, but I’ll be sure to do a show HN when it happens!
Please ping me as well - I'd certainly be interested.
It seems like the main difference with a reducer is that the reducer just returns `state`, not `(state, action)`. I'm reading this for the first time but that's how I'm reading it.

But y'all must be storing this `(point-in-time-state, action-that-produced-state)` somewhere, because the Redux Chrome plugin displays this data. Was I wrong in thinking the Chrome plugin was just visualizing data already stored by Redux?

No, the core Redux store does _not_ store any action history by itself. It's simply:

    function createStore(reducer) {
        var state
        var listeners = []
        
        function getState() { return state  }
        
        function dispatch(action) {
            state = reducer(state, action)
            listeners.forEach(listener => listener())
        }
    }
It's the Redux DevTools that do the real work of actually saving an action log, like what was described above. In fact, canceling actions does actually work by re-running the actions in the same sequence, minus the ones that are being skipped, to generate the new state.

That logic has been split out into its own package, which you can see in the https://github.com/zalmoxisus/redux-devtools-instrument repo.

Very vaguely related side note: earlier this year, I spent a couple days to add an "action stack trace" tab to the Redux DevTools Extension. When you click on an action, in addition to seeing the action contents, state tree, and diff, you can now also see an actual formatted stack trace that shows you exactly where the action was dispatched from (including the actual code if you've got sourcemaps enabled). Sadly, the extension maintainer has been MIA recently, so we're considering forking it into the Redux org. Until then, you can download my custom build of the extension here: https://github.com/zalmoxisus/redux-devtools-extension/issue...

That's really interesting. I honestly haven't given Redux DevTools much thought despite the fact I use it daily as a core part of my workflow. I thought, like I said before, it was mostly just visualizing artifacts produced normally by Redux. Makes the browser extension much more interesting!

edit: Here's the link where it's implemented in redux-devtools if anyone else is curious https://github.com/reduxjs/redux-devtools/blob/master/src/cr...

This was also acknowledged by Greg Young a long time ago :) [1]

[1] maybe this talk? https://www.youtube.com/watch?v=JHGkaShoyNs