React Is Eating Itself

Adam Nathaniel Davis - Mar 9 '20 - - Dev Community

A few posts ago, a thoughtful commenter said they'd like to understand "why React is so compelling for you". I tried to outline some of those reasons in that post (because Redux goes against so much of what I find beautiful in React). But I didn't really explain how core React can be so elegant. Nor did I properly highlight how so many current-day practices are slowly eroding that elegance.

(That prior post was titled The Splintering Effects of Redux and can be found here: https://dev.to/bytebodger/the-splintering-effects-of-redux-3b4j)

"Locus of Control" vs. "Separation of Concerns"

When nearly all of our enterprise applications were delivered by server-side processing, MVC ruled the day. MVC was a useful pattern because it kept us from blindly shoving ALL THE THINGS!!! into a single class/page/module/function. It made us more vigilant about separating data (Model) from display (View) from logic (Controller).

If there's any "problem" with this pattern, it's that it started to get... "fuzzy" as our apps got pushed mostly, or entirely, into the UI layer. There are still devs who try to adhere to the idea that all data calls should be separated from all display which should be separated from all logic. But that paradigm doesn't provide as much value in a Single Page Application.

The current generation of "rich" internet applications makes these distinctions challenging (if not outright erroneous). Does that sound like heresy to you? If so, consider that the more real-time processing capability which gets pushed-to/built-in the browser, the more the browser effectively becomes a true console.

Have you ever built a true console app?? (It's OK if you haven't. But it's useful to this topic if you have.) Although it may feel archaic today, if you ever built, say, a small Visual Basic app designed to run directly in the operating system, you might start to feel what I'm getting at.

In a console app, you typically have a variety of components that you can position somewhere on the screen. Most of those components come with a set of common features:

  1. Attributes that control the component. Usually, these attributes define the component's initial appearance/behavior.

  2. An internal store that holds ongoing information about the component. This could include: the component's position, current display features, information about related components, etc.

  3. Pre-existing or programmer-defined actions. These events are frequently triggered by a user's interaction with that component.

  4. An interface for this component to "talk" to other components, or to interact with other data stores.

  5. Some components are standalone. But many are container components, capable of housing one-or-more child components.

Notice that there's nothing in this component model that even attempts to satisfy an MVC pattern. Under a rigorous MVC approach, the component's own internal memory would be handled somewhere else - in the Model. Any of the logic that is triggered through its actions would be handled somewhere else - in the Controller. Even any tweaks to the component's display features would be handled somewhere else - in the View.

So is a console-application component somehow "bad" programming? After all, here we have one "thing" - a component - that has logic, and data, and display all wrapped up in one bundle. So that's gotta be a problem... right??

Umm... no.

You see, the console component that we're talking about here can reasonably handle logic and data and display, all wrapped up into the same "thing", because we only give that component power over those things that should naturally be in its locus of control.

In other words, the console component can (and should) handle the data (the Model) that belongs in that component. It can (and should) handle the display (the View) of that component. It can (and should) handle the logic (the Controller) to process the actions that are triggered from that component.

A Different Kind of Console

With every new browser update, they come ever closer to being true consoles. And if you're a React developer, a lot of this verbiage probably sounds very familiar to you.

React has components. Those components (can) have their own internal state. Every component has a render() function to handle its own display (which can return null if there is no display to be rendered). And they can have any number of associated functions, which handle the logic associated with their own actions.

This can all be demonstrated with the most basic of examples:

import React from 'react';

export default class Counter extends React.Component {
   state = {counter:0};

   decrement = () => {
      this.saveCounter(this.state.counter - 1);
      this.setState(prevState => {counter:prevState.counter - 1});
   };

   increment = () => {
      this.saveCounter(this.state.counter + 1);
      this.setState(prevState => {counter:prevState.counter + 1});
   };

   render = () => {
      return (
         <>
            <div>Counter = {this.state.counter}</div>
            <button onClick={this.increment}>Increment</button><br/>
            <button onClick={this.decrement}>Decrement</button><br/>
            <button onClick={this.reset}>Reset</button><br/>
         </>
      );
   };

   reset = () => {
      this.saveCounter(0);
      this.setState({counter:0});
   );

   saveCounter = (counter = 0) => {
      fetch(`https://127.0.0.1/saveCounter?counter=${counter}`);
   };
}
Enter fullscreen mode Exit fullscreen mode

In this scenario, I'm thinking of the entire <Counter> component as, essentially, a "thing". A "logical unit", if you will. So even though there's a lot going on with this little example, it's all part of one logical unit.

The <Counter> component has its own memory (state). But that actually makes sense, because the only memory it's responsible for is related directly to this logical unit.

It has its own layout (rendering). But that makes perfect sense, because it's only rendering the items that are directly related to itself.

It has actions - and the logic needed to process those actions. But again, that makes perfect sense, because those actions are all directly related to itself.

And finally, we even have the initial phases of a data layer, as witnessed in the fetch() inside saveCounter(). But that makes a lot of sense here, because the data it's saving is specifically related to itself.

In other words, even though this one component does rendering, internal data, external data, and logic tied to actions, that all makes sense. Because all of that stuff falls under this component's locus of control.

I'm not gonna lie. I see a certain beauty in this. If I want to know what's going on with a particular component, I look right inside the component's code. I know... radical concept, huh? And it's not like I'm just making this stuff up on my own. When you look all over React's core docs, they give many examples that are very similar to this.

But code like this is becoming increasingly rare "in the wild". The beauty of this model is disintegrating - because React is eating itself.

This Is Why We Can't Have Nice Things

Outside of blogs and tutorial sites, you rarely see much code like above in "real" applications. And I don't mean just because the above example is small/simple. I mean, because React devs have been demonizing many of the simple concepts illustrated in this example. They keep picking at this basic framework until the result is hardly recognizable.

Separation of Concerns

MVC may not be "a thing" much anymore, but it still hangs heavy in many minds. I've received feedback, from other professional React devs, that an example like the one above violates separation of concerns. Of course, for all the reasons that I outlined above, I think that's flat-out ridiculous. But nevertheless, many React devs seem to have some kind of fear about putting too much "logic" in any of their components.

The last place I worked, they literally created two components for every one. The first component held the render(). The second one held all of the functions that were used in that component. They called this sibling component the dispatcher. Then they bound all the functions from the dispatcher to the first component. And they somehow thought this was a brilliant way to foster separation of concerns. I thought it was abject idiocy.

The more you do to fling these functions into far-off files/directories, the more obtuse you make your app. And the more difficult you make your troubleshooting.

The way we build applications today is like building a car and deciding that the engine should be in Chicago, the wheels and driveshaft should be in Atlanta, the gas tank should be in Seattle, and the cabin should be in Dallas. And then we congratulate ourselves because we have separation of concerns.

The problems arise because we all have nightmares of apps that we had to maintain in the distant past. Horrific "vehicles" that included an engine, a coal-burning power plant, a Victrola record player, a toaster oven, and three broken down analog televisions - all crammed side-by-side in a single file/class/function/component. And we've been so traumatized by that experience, that now we try to build new cars with every different part flung out to far-off places. But we rarely stop to think, "Wait a minute. What are the parts that still belong together, in one place, very near each other?"

Obsession With "Purity"

React/JavaScript devs these days are obsessed with the notion of purity. Pure components. Pure functions. Pure dogma. These devs will gladly chug a pint of bleach - as long as you assure them that it's absolutely pure bleach.

Look, I get it. As much as you can, it's useful to break down your app into as many "pure" components/functions as possible. That purity leads to easier testing and fewer bugs. And the example above is definitely not "pure".

But you can't build anything bigger than a blog demo without eventually having to create some "impure" components/functions. Your app will need to have some kinda state, and external memory, and side-effects. It'll need to talk to some kinda data store. And there's no way to do those things without violating the Holy Scripture of Purity.

The State-Management Nightmare

One way that devs strive for more "purity" is by chunking some big, heavy, state-management apparatus into their app and then allowing it to handle all that nasty, dirty, impure state/data management stuff. So they'll take a component like the one above, and when they're done with it, it will basically be left with nothing but the render() function. Then they'll strain an oblique trying to pat themselves on the back because the refactored component is so "pure". But that's not purity. That's obscurity.

Sure, we could handle most of this oh-so-evil logic in reducers and actions and subscribers and all sorts of other state-management constructs. Then, when we open the code file for this component, we'd be all self-satisfied with its "purity". But... the component wouldn't make any sense.

With state-management thrown into the gears, you'd open this file and have a hard time figuring out how the counter is set. Or where it's set. You'd have to trace that logic through directories/files that "live" nowhere near this one. And somehow, React devs think that's... a good thing???

Klasses R Stoopid

Sooo many React devs nowadays wake up every morning and sacrifice a fatted calf and their first-born child on the Altar of Functions. They're brainwashed by the React Illuminati that any code with a class keyword in it is somehow Evil & Stooopid. And any code that consists of only functions is Holy & Righteous.

They can rarely articulate any empirical reason why these demonic classes are actually so... "bad". They just furl their brow, dig their nose, and mutter something about how "Classes are da sux. And yer stooopid."

It's not that I don't have empathy for the class haters. It's a big word. It's too confusing for all but the most advanced of programmers. It's got that "OOP-shtank" all over it. You can't be expected to put up with code that actually has a class keyword in it! That's just not fair!! You're perfectly within your rights to curl up into the fetal position any time you so-much-as look upon that scary, nasty, horrible class keyword.

This is not some diatribe against functions. Functions are beautiful. Functions are great. But in the example above, everything shown there is part of a single logical unit. We could create a single counter.js file that has all of the functions defined on this page, outside a class, but that would only obfuscate the original intent of this single component.

What many in the class-hating, function-worshipping crowd seem to miss is that, in this context, the class is a logical namespace for all of the data/display/logic that should be tied to the <Counter> component. Yes... you could break that up into a series of loosely-connected functions - but that serves no logical purpose, other than to appease the Function God.

(If you want to get my full breakdown regarding the abject silliness of your class hatred, check out this post: https://dev.to/bytebodger/the-class-boogeyman-in-javascript-2949)

Anything Outside of a Hook is Stoopid

I won't go into this point in toooo much detail, cuz it's kinda an extension of the previous point about classes-vs-functions. But nowadays, even if you LOVE functions. And even if you publicly DENOUNCE classes. That's... not good enough for the elitists. If you haven't spent your nights/weekends/holidays figuring out how every dang snippet of code can be refactored into a Hook, then you're just a script kiddie posing as a "real" programmer.

The Hooks crowd feels downright cultish to me. There are already sooo many examples I've seen - on the interwebs, or in person - where someone takes a class-based component that's supposedly bad/wrong/evil, then they refactor it into a Hook that has just as many LoC - maybe more, and they feel all self-satisfied, like they've done something special and they deserve a cookie. And a smiley face. And a bowl of ice cream, with extra sprinkles on top.

Loss of Focus

In the "default" React framework, there's a real beauty in setState(). setState() is only designed to work on the component where it's called. In other words, setState() is specifically confined to that component's locus of control. Of course, you can pass a state variable down to the descendants. You can even pass a function that will allow the descendants to invoke a change on that state variable. But the actual work of updating that state variable is only ever done inside the component where it resides.

This is critical, because state-management tools throw this concept out the window. And once you throw that concept out the window, you start implementing a whole bunch of clunky constructs (like reducers and actions) in an attempt to shove that genie back in the bottle.

But you don't have to jump through all of those hoops if you keep state where it "belongs" - inside whatever component naturally should control it. This allows you to keep all of the updates for those state variables in one, logical place.

All of the obfuscating overhead of tools like Redux comes about because they're trying to recapture the control of state updates that already existed in React's default model.

Conclusion

Despite what this might read like, the fact is that I don't much care if you're using Redux (or other state-management tools) on your projects. I don't care if you want to split all of these functions off into their own far-flung directories. I don't care if you think I'm an idiot because I (continue to) commit the sin of using the evil class keyword.

But so many of these fads that have swept through the React community (and they are fads) have the very tangible effect of degrading what was, originally, a very beautiful framework. It's only a matter of time before someone comes up with a Hooks replacement, and then they'll be telling you that you're an idiot for using those old, washed-up constructs. (Even though they won't be able to give you any empirical reason to backup their contentions.)

So much of what made React amazing in the first place has now become rare in "real" React applications. The React Illuminati have spent so much time trying to craft fixes/replacements for original React features (that were never broken to begin with), that now we have React apps/components that are harder to troubleshoot than spaghettified jQuery apps.

You can rarely ever just open the code for a component and see what it's doing. The elitists have flung all of the logic into the dark corners of the application.

I'm not saying that every React component must/should look like the one above. But the farther we stray from that model, the more we undercut many of the things that made React great in the first place.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .