Redux without Redux

From the Redux testimonials:

“It’s cool that you are inventing a better Flux by not doing Flux at all.”
–André Staltz, creator of Cycle

I recognize this may be a classic case of me not seeing the forest for all the trees.  I also recognize that I am writing this post before I have gathered the level of intel I would normally gather before writing a post like this.  But, considering my current activities, I figured it could be a long while before I reach that point on this topic.  Thus, I am starting the conversation sooner than later — possibly at my later embarrassment.

Back Story

I recently began work on my first React app — something I’ve been itching to do for a while now.  I did my homework and learned, up front, that Redux is the preferred tool for state management.  I took a brief look at it, agreed with what it was promoting, then put it aside to come back to later.  (I also took a long look at Relay but dropped it when I found that the Python back-end tools are not production ready yet.)

I began work on the front-end without Redux because I had enough to learn already with React and react-router (as well as Python, Flask, SQLAlchemy, and Postgres — all new to me as well on this project).  I got some of the basics running pretty well and then took a step back to learn Redux.  According to my timesheet, I spent 100 hours in the Redux docs and billed only 10 hours as progress on the app.  As I was working Redux into the code, however, I began to see how it was blowing the structure of my code apart.  I stuck with it a little while longer trying to understand the madness of it.

What was troubling me, however — what was nagging me — was that even though I had read through Redux’s basic tutorial, much of the advanced tutorial, and a few other sources here and there — every time I went back to Redux’s Todo List example code, I found it very hard to read and understand.  This calls this, which calls that, which calls this, etc., etc.  I normally don’t have this much difficulty grokking new tech.  I knew what this was but I didn’t want to believe it.  We had a word for it back in the day when “best practices” was thought of as something good: spaghetti code.  [Did you see that?  Half my readers just left at the horror of my accusation of one of their favorite tools.]

What also troubles me about the Todo List example is that it stops short of being a useful example.  Like they quit and declared beer time just when it was about to get really good.  A useful example would then persist the data and also do some dummy server-side logic that might reject the update or change the data set (perhaps a plagiarism check [uniqueness] and a grammar check).  (Do we really think we can duplicate all business logic on the client — and, even if we try to: Would it be wise to not double-check what’s coming in before we put our fortress of data at risk?) You can see little bit about how Redux expects you to handle fetching data in the advanced examples but you’ll be hard pressed to find the big picture on persistence (I never found a good example for it).

So, what did I do?  I threw Redux out the window (and redux-devtools, and react-redux, and react-router-redux… sheesh!) and I’ve been happier ever since.

Now What?

What I instantly liked about React, when I first learned about it through reading Angular- vs.-React articles, is that it throws out a lot of the new-age OO chaos and takes a step back into the procedural world.  The horror!  The audacity of it!  And I doubt they’ll ever admit it.  But, I grew up in the procedural world so I’m “jiggy with it.”

To me, building with React looks a lot like building with server pages.  Just on the client side instead of the server side.  Thus, I think of React as “client-side server pages” which, well, really makes no sense except that it makes me smile at the oxymoronicness [Is that a word?] of it.

With Backbone.js, it became clear immediately that programmers can create a real mess with it very easily.  It seems you can scatter business logic anywhere throughout the MVC model and I’ve seen Backbone code from supposed pros where you can’t tell the view from controller by looking at the code alone.

When I learned AngularJS, it was like the heavens opened “Alleluiaaaaa!” and JavaScript had now been brought up to speed with 1990’s tech of VisualBasic’s data binding.  And, yet, still, as beautiful as Angular is, people still can’t keep view and controller code out of each others pockets.

And now we have React that stands up and says “To heck with a lot of the templating and overly-done OO scatter braining, and let’s do OO right!  By taking a step back into procedural and keeping code that belongs together, well, um, together.” You may disagree with this summation, but it works well for me.

Then along comes Redux and it says “As much as we like you, React, we think your paradigm of keeping code-of-a-feather together is stupid.  So, let’s start blowing it apart again into separate files like all the other crappy MV* frameworks do.” Redux might as well be called Antireact from this particular point of view.

At this point, it is probably sounding like I’m a Redux hater.  I’m not.  I hate the time I spent with it before finally discovering that I don’t need it.  But, I don’t hate Redux.  I can still believe that I might find it useful in super apps that have features unlike anything I am doing right now.  I suspect I just don’t know enough about it yet other than how hard it has proven to get it into my head.  I get it somewhat.  I get what it is trying to do and why.  I just don’t get why so many people think all the convolution is worth it.

According to Redux, this is what they are selling: “a state management library with minimal API but completely predictable behavior, so it is possible to implement logging, hot reloading, time travel, universal apps, record and replay, without any buy-in from the developer.”

Sounds great but that is a long list of wow that I don’t need (and probably why I don’t need Redux).  Do you need all that?  To me, in more basic needs, what Redux is selling what Flux was selling: a central point for state management and relief from repetitive and bulky property loading (a.k.a. “drilling your application”).  These can only be good things, right?  Well, yes, as long as you don’t sacrifice the primary rule of enterprise code:

Code should be self-apparent.

Okay, I admit it, while I might be the smartest programmer on the block, I’m not the smartest programmer in town.  Perhaps, if I had a bigger brain and someone to introduce Redux to me in a way that I could see the pros and cons of it versus alternatives more clearly, I might like it (aside from the code-splitting among files).

I know my weaknesses pretty well — it is, after all, a lot of work for me to crunch data models and object models in my head.  I don’t build anything until I get a working model in my head and this process can be quite exhausting.  I am often found staring into space for hours while crunching, and re-crunching, and re-crunching models in my head until I find the model that works best.  Ugh!

This kind of model refactoring is what I did with my current React app, before and after I tried to work Redux into it.  I had crunched the React component object model over and over again until it looked nothing like my data model and felt like it would be React friendly.  It’s been an exhausting trip — more so with learning so much new stuff — but I think the resulting code clarity is worth the effort.  I’m still in the thick of it and not done crunching yet so, who knows, maybe I’ll eat my words by the time I’m done.

The Way

What I’ve done, instead of using Redux — and going back to where I was headed in the first place — is pull in the entire data set into one of the container components near the top of the app (after login and other global selectors).  This is a common design pattern for me — if the client can hold it, and has any smarts to process it, I’ll fetch the whole shebang with tuned joins and leave the database alone as much as possible.

This particular app is fairly small in the enterprise scheme of things.  It represents 14 tables, has components about 10 layers deep, and splits into 3 or 4 parallel branches.  Some of the main interface components are unique and must be custom built and they present an unusual workflow that is challenging to accommodate.  Thus, it seems to be a fairly small app without a lot bells and whistles in spite of some of the complexities.  Yet, on the other hand, all of the in-house enterprise apps I have ever built are about this size.  Even though I usually write apps for databases which contain hundreds or thousands of tables, most apps just focus in on updating a dozen or so of those tables — not unlike this one.  Anything larger tends to get split up into collections of apps.  Therefore, even though I call this app small, it is still a fair test for me and the work I do.  If I were building something huge, like my Facebook page, it would be absurd to try to build for all that content in one state anyhow.  You always have to divide and conquer at some point.  Likewise, this app will have an admin interface that will likely contain its own state in its own container component.

Okay, back to it.  So, I have fetched the whole shebang and stored it as state in an upper-level container.  Thus, I now have a central point for state management (effectively).  Now, how do I manage it without excessive props passing?  I do that in good, old-fashioned, OO style.  “What!?  Shocking!  Is Matt going OO on us?” Or, maybe it’s backwards OO style — I can’t quite compute what to call it.

To avoid excessive props passing, some of my child components also act as sub-containers.  That is, they peel off part of the large data object passed down to them and make it stateful.  They also then provide a lower-level API that can be passed as a single prop to its child components, and so forth.  These APIs allow the children to mutate the mid-level states.  When the mid-level states need to persist or otherwise pass state up to the parent, they use the higher level API to pass their larger state chunks up.

It doesn’t have to be this rigid if it doesn’t fit the app.  For example, a mid-level state could persist itself and then send a message up to the parent to refresh the shebang.  Or, maybe, it received the shebang itself and passes that up instead of a message.  A lot of this depends on how you design your REST API and what it returns.  I, personally, don’t like such deviations from the structure but I know they are there should I need them.

Thus far, I’m keeping things together well and not doing anything surprising to my current design pattern.  All persistence so far is being done by the parent container.  To be fair, however, I’m building an app that is heavy on data collection and forms, so I have clear submit events to persist on.  I am not, for example, persisting on every change or blur event.  We don’t have the server budget for that kind of traffic and this app doesn’t really call for that kind of behavior anyhow.  If I did need to build more messaging between my objects, I suspect would still follow this pattern versus Redux.

For example, my parent container might pass props like this (assuming react-router is in use and React.cloneElement is needed to pass props to unknown children):

render() {
    return (
        React.cloneElement(this.props.children, {
            theShebang: this.state.theShebang
        ,   submitThis: this.submitThis.bind(this)
        ,   submitThat: this.submitThat.bind(this)
        ,   submitTheseThings: this.submitTheseThings.bind(this)
        ,   submitThoseThings: this.submitThoseThings.bind(this)
        })
    )
}

Then, the child might take this.props.theShebang and peel off part of it, re-normalize it if needed, and make it stateful.  It might then pass more-detailed, mid-level APIs to its children like so:

render() {
    return (
        <ThisAndThat
            someShebangPart={this.state.someShebangPart}
            prop1={this.props.prop1}
            prop2={this.props.prop2}
            thisAPI={{
                handleSome1Change: this.handleSome1Change.bind(this)
            ,   handleSome2Change: this.handleSome2Change.bind(this)
            ,   handleSome3Change: this.handleSome3Change.bind(this)
            ,   handleSome4Change: this.handleSome4Change.bind(this)
            ,   handleSome5Change: this.handleSome5Change.bind(this)
            ,   handleSome6Change: this.handleSome6Change.bind(this)
            ,   handleSome7Change: this.handleSome7Change.bind(this)
            ,   handleSome8Change: this.handleSome8Change.bind(this)
            }}
            thatAPI={{
                handleSomeAChange: this.handleSomeAChange.bind(this)
            ,   handleSomeBChange: this.handleSomeBChange.bind(this)
            ,   handleSomeCChange: this.handleSomeCChange.bind(this)
            ,   handleSomeDChange: this.handleSomeDChange.bind(this)
            }}
        />
    )
}

Those children might then pass APIs further down:

render() {
    return (
        <ThatPart
            somePart={this.props.someShebangPart.somePart}
            prop1={this.props.prop1}
            prop2={this.props.prop2}
            thatAPI={this.props.thatAPI}
        />
    )
}

So, sure, it can get bulky in places but, if you design your objects well, it shouldn’t get unwieldy.  Whether the Redux way or this way, you’re going to have a long list of change handlers somewhere.  I, clearly, come from that the camp that code that is logically grouped together, and contains few deviations from baseline practices, is easier to maintain.  (Also, keep in mind that one or two of your globals are probably in the URI and found in this.props.params.<id> and, thus, don’t need to be passed as props explicitly.)

You might have also noticed that I mentioned “re-normalizing” something up there.  This happens.  Your RESTful services may not always deliver up data normalized in the best way for your components — especially if you grab the whole shebang like I do.  Redux also acknowledges this.  Redux suggests using normalizr.  Okay.  Perhaps.  Personally, thus far, during state construction in the component, I parse the data if I need to using unsurprising JavaScript and write out a new object to make stateful.  Alternatively, I could build a new service and fetch it how I need it but, like I said before: Why bother the database if you don’t need to?

Tangent Notes

Those who know me know I’m not a fan of frameworks that take OOD or OOP to such absurd levels that they defeat themselves.  In my opinion, if you have to hit 3 files just to maintain a single object (and not counting parents or children) then you are doing it wrong.  I’m looking at you, Rails!  In particular, I find Rail’s ActiveRecord ORM to be a great example of how to do OO wrong.  I’m not any sort of OO expert — I already told you I grew in the procedural world — but it so astoundingly obvious how wrong ActiveRecord gets it that I find it amusing that the great OO Ruby community embraces Rails.  Why am I talking about his here?  (1) I’m human and I can’t help but point this out every chance I get.  And (2) it’s to make a point about design paradigms and sticking to them… or not.

The big failure with ActiveRecord is that it limits design on both sides of the ORM fence — a very critical fence, by the way, that performance of your application hinges on.  ActiveRecord, it seems, supports third normal form at best [someone’s bound to correct me on this and that is fine; whatever].  Thus, chances are, you are not using the full power of your RDBMS to protect your data — your most valuable asset.  Who would do that?!  ActiveRecord also forces your business objects in Ruby to mirror your tables in the database.  Since when has a good object model looked like a relational data model?  Really?  I find this absurd… and, yet, I admit, the active record pattern has its uses in places.  Sometimes you just don’t need the full power of an RDBMS and sometimes you don’t need the smartest object model.  Sometimes convention over configuration is just the rapid development edge you need.

This is what makes ActiveRecord such a great analogy here [except, maybe, in reverse].  Like ActiveRecord, I can see where Redux may have its place.  Also, like ActiveRecord, I would think long and hard about whether it really is for you.  Unlike ActiveRecord, however, you can get a quicker start without Redux than with it.  I’m still not sure, however, where the good Redux use cases start and stop.  If you try to over-simplify the choice to use Redux or not by saying that my use cases aren’t complex enough, or big enough, to warrant Redux, then I think you are making a mistake.  It is different than that.  I don’t think it is about size of application.  It may be about complexity… but complexity is very subjective and I suspect it is more about the types of things your application needs to do.  Even if this app were 10 times bigger, I don’t see changing design patterns.  It is something else — something I don’t yet have enough experience with to identify, I suppose.  Perfectly possible.  This app and the tools I build for enterprise are nothing like social networking apps.  On the other hand, perhaps it is just mindset and point of view.

This brings me to my final point: Are you really sure you want to change React this much?  Those who know me also know that I’m not a fan of too many add-ons.  Many of the packages I’ve looked at recently (for both Python and JavaScript) seem to be nothing more than a way to save a few keystrokes on a few lines of code.  They do this by implementing some sort of domain-specific language (DSL) through decorators and whatnot.  If you grab more than a couple of these, then you’ve just created a DSLM™ (DSL Monster).  This is great for job security because it is bad for the new guy.  And what is bad for the new guy is bad for maintenance costs.  Sure, I know people that can eat this stuff up quickly and mashup DSL quickly, but not all good talent is like that, and not all talent like that is good.

Conclusion

At the moment, I would have to say that my basic conclusion is that if you need only the basic features of Redux (similar to the basic features of Flux), then skip Redux (and Flux) and do it different.  If you need the advanced features of Redux (as listed earlier), then choose carefully what kind of learning curve you are willing put on new talent.  Maybe Redux, maybe something else.  If I was thrown into the middle of a Redux app, I might understand it easier — often it is easier to understand by modifying than to start from scratch like I have attempted.

Redux is a huge change to the beauty of React with documentation that failed me.  Yet, it appears to have some spectacularly smart people behind it and many others find it useful.  You decide, but… “Do not go gentle into that good night.”

I look forward to digging into Relay and Falcor more.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s