DEV Community

Cover image for React Is Eating Itself
Adam Nathaniel Davis
Adam Nathaniel Davis

Posted on • Updated on

React Is Eating Itself

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.

Top comments (46)

Collapse
 
isaachagoel profile image
Isaac Hagoel

Man.. the speed in which you generate these posts is epic.
"Production quality" React code bases in the wild indeed tend to become a convoluted mess.
Do you have any experience with other modern frontend frameworks (specifically Svelte comes to my mind)?

Collapse
 
mateiadrielrafael profile image
Matei Adriel

Svelte has poor typescript which leads to an even bigger convoluted mess (tried it in a side project, not going back until it gets first class ts support)

Collapse
 
isaachagoel profile image
Isaac Hagoel

Well.. I used this on a side project and it worked fine (svelte, sapper and typescript).
Typescript is a whole other discussion but in general it does not reduce the convolution of a codebase (if anything it adds complexity and in return you get its advantages)

Thread Thread
 
mateiadrielrafael profile image
Matei Adriel

I tried that monorepo relatively recently and it totally broke vscode, errors appeared in random places, my code wasnt compiling and more... Maybe it was a bug they fixed right after, but it's definitly a bad first impression...

On typescript: I think it prevents you from doing some stupid stuff which would convolute your codebase

Thread Thread
 
isaachagoel profile image
Isaac Hagoel

Too bad you had that experience. It worked for me in vscode just fine (maybe I needed to install a plugin or two). If your thing is on GitHub maybe I can have a look.
I am curious, how can typescript prevent you from convolution your codebase (I am not anti typescript or anything)?

Thread Thread
 
mateiadrielrafael profile image
Matei Adriel

Typescript:

  • removes the need of manual type checks for internal code (u only need to do it for stuff like api responses)
  • limits your ability of doing random js magic which isnt rly typesafe

Noe that I think about it, it doesn't prevent u from writing messy code that much, but I guess its something? Idk

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

I'm not anti-TS either. But I've always maintained that dynamic typing is a feature. Not a bug. Static typing is good. It's powerful. But it's not the end-all/be-all. And there are some times when dynamic typing can actually be very useful.

Thread Thread
 
mateiadrielrafael profile image
Matei Adriel

Can you give a moment where dynamic typing is useful? Not saying ur wrong, just cannot think of any myself

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis • Edited

The most common example (IMHO) is on return types. Now, don't get me wrong: I think you can take this concept wayyyy too far. And I'm not saying this should be done all the time - or even most of the time. But it's not terribly uncommon in JS to have getName() return a string if there is just one name, or an array of strings, if there are multiple names.

I know that, in TS, we have union types. Those can help us consume functions that return multiple types. In a "true" statically-typed language, that's not an option. Like, in Java, you can't designate a method as returning a string or an array (although you can accomplish something similar by using generics - which are handled much better in C#).

I often use dynamic typing as a type of semaphore to indicate different stages in a variable's "life cycle". For example, I have a frontend app that has to call to an API to get all of its data. The data sits originally in state values like this:

this.state = {
   roles : false,
   teams : false,
   users : false,
};

Obviously, the API calls will run asynchronously. And with React's render cycle, it can be tricky to assure that we're not calling the same endpoint for data that we've already received. So in any code that would invoke an API call, there is a check that looks like this:

if (this.state.roles !== false) 
   return;

I don't want to set the initial state values to empty objects, because it's at least possible that the API will return an empty object. So then there would be no quick-and-easy way to simply look at the state variables and know whether they've received the API data load.

I could just set the original state values to null. Or I could set additional variables like hasRolesApiBeenCalled and hasRolesApiResponseBeenReceived, but that starts to become a headache. And seeing the false value in my code is a clear visual indicator (to me) that we're checking to see whether anything's been received from the API at all. Because the simple value of false would never be a valid, standalone value received from my API.

Thread Thread
 
mateiadrielrafael profile image
Matei Adriel • Edited

Heres an example solving the first issue with getName:

type User = {
    name: string
}

const getName = <T extends User | User[]>(u: T): T extends User ? string : string[] ....implementation

For the semaphore example I dont really get the problem, cant you do somsthing like:

type Semaphore<T> = {
    [K in keyof T]: K[T] | false
}

I typed those on my phone so idk if I didn't make any typos or stuff, but the logic is there.

Maybe I understood the problems you enumerated wrong, if that's the case than sorry I guess:)

I only used f#, so idk too much about c#, what features do c# generics have you'd like to see in ts?

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis • Edited

The original question was in regards to where dynamic typing could be "useful" - not required. So from that perspective, there's really nothing to solve in my examples. I'm just explaining to you where dynamic typing can be useful - to me.

Every programming problem can be "solved" with static typing. There is no scenario where we can say, "Well, this particular problem simply can't be solved unless we switch to dynamic typing." (Conversely, every programming problem can also be "solved" with dynamic typing as well.)

What I see in your examples is what I often experience from devs who are firmly ensconced in static typing. They look at every approach that involves dynamic typing and they say, "Why couldn't we do it with static typing this way??" And of course, the answer is always that you could certainly do it that way. And there's certainly nothing wrong with approaching programming tasks through a static-typing lens - just as there's nothing wrong with doing them through dynamic typing.

My only "issue" is that the static-typing crowd tends to look at dynamically-typed languages as though they have a bug or a flaw that they're constantly trying to fix. But dynamic typing isn't a bug in a language. It's a feature.

Now... you may not like that feature. You may prefer to work in languages that don't have that feature. And that's fine. We all have our preferences and our own way of grokking code. But the plain simple truth is that JS is, and has always been, a dynamically-typed language. And there's nothing necessarily "wrong" about that.

If you feel that a particular project is best addressed using TypeScript, then that's cool. I'd probably agree with you. If TypeScript is your go-to tool-of-choice on nearly any project, I wouldn't necessarily agree with you - but I get it. But if you're thinking that JS's dynamic typing is an inherent flaw that needs to be completely washed out of the language, then I'd say you're working in the wrong language. Because dynamic typing wasn't some bug in the language's source code that needs to be "fixed".

Thread Thread
 
mateiadrielrafael profile image
Matei Adriel • Edited

Fair enough, I now get what you are trying to say:)

A nice approach is the one taken by f# - being statically typed but being able to infer almost everything, but sadly js has a bunch of wild parts which you cannot really infer the result of

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

I've been looking at Svelte - a lot! It has some great promise - although I gotta admit that the whole dependence upon templates kinda makes me wary. I can't see a templating architecture anymore without thinking of Handlebars - which makes me throw up a little bit in my mouth.

But aside from that, it looks really cool. The only downside to some of these other tools is that it's one thing to read up on them, or play around with them. But to actually do a lot of serious coding with them, it'd all have to be in my free time - because my "day jobs" are typically consumed with the established paradigms - like React, or Angular, or (yes, even) jQuery.

Collapse
 
isaachagoel profile image
Isaac Hagoel • Edited

I wholeheartedly agree with the sentiment you express around "you don't know how good a tool is (and don't actually understand it) until you do serious coding with it".
Templates are an interesting topic. They have some advantages especially around predictability (which also helps the compiler afaik). In other frameworks that used templating I often felt limited by it (especially when the template was in a different file than the JS code).
In Svelte I haven't felt that so far. If that moment comes I will probably start resenting it as well :)
Actually, having the ability to use stuff like #await, custom-actions and variables from the store right in your template feels really expressive and empowering to me.
TBH, I didn't really understood what actions are about (and didn't make use of them) until I watched this talk:

Anyhow, I am yet to see a huge application built in Svelte and React does put food on the table, but so many of the design decisions and tradeoffs Svelte makes strike me as fundamentally better than React's.
In the context of this post, the Svelte solution for 'store' comes to mind. Would be interesting to directly compare and contrast it with React context and Rudux :)

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis • Edited

Agree on all fronts. Even though I've been coding since I was (literally) in junior-high, I've actually been a late adopter on many new languages/technologies. I hate the experience of falling in love with a language/framework, only to see it fall into obscurity. The last time I did this, I was a fairly-early adopter on Flex. And we know where that ended up...

So I guess you can say that I'm kinda "lurking" on Svelte. I'm genuinely excited about it. But I'm not gonna be that guy converting my HUGE personal project over to it unless I start to feel that it has some serious "legs".

Collapse
 
louiszen profile image
Louiszen • Edited

Just a syntax perference.
Many hook programmers have many wrong basic concepts:
1) Adding the keyword "function" will become functional programming, while adding the keyword "class" is OOP. (In fact, they are not exactly the opposite, but much closer to the concept of how you organize your procedures. In fact, I taught my students they should have a mind set of both FP and OOP, best coding needs FP's stateless procedure isolation and OOP's code hierarchical organization)
2) Hook shorten codes, so they can add TypeScript to that.
(Hook are somehow contradict to the concept of TypeScript, hook wants to bypass framework restriction, while typescript is adding restrictions on variable type)
3) Static classes are evil, all non-rendering things should be in Hooks.
(I think they regard React as a language more than JS)
4) Misunderstanding the behavior of the followings: (function pointer and execution of functions)...
func
func()
() => func
() => func()
() => {func()}
because useState in fact is something like this I guess? they implicitly execute the function pointer in useState.
useState = (val) => {
this.v = val;
return [
this.v,
(v) => {
this.v = _.isFunction(v)? v(this.v) : v;
}
];
}
5) Hooks can shorten class!... (but in fact the main working code cannot be simplified, and boiler plate still exists, why not use a code template generator?)
6) Tends to build small unorganized things with high coupling because they like to make every function component stateful
7) Keep saying that hook improve the performance (but in fact not)
8) Keep saying that hook can minimize bugs, (but they even dont know when the function is executed)

Hook can be easily translated to Class, but not every class can translated to Hook.
I wonder who invents hook is come from the decades of ES5?
The coding style of hook I can find them appearing in most of the buggy systems which is full of callback hell in the days of ES5.
Everyone knows that class is just a syntax sugar in ES6, the undereath is still a function.
Just like async/await, a group of people still insist to write callback hells.

I do think that every syntax have their beauty and someone can code Hooks in a nice way.
But I cant meet one, most of them having that ability will go for class.

Collapse
 
tronicboy1 profile image
井上 オースティン

I love the points you raise here! Still holds very true in 2020.

I think Web Components API offers a great alternative to what React did really nicely back before hooks, without the framework.

I drank the Hooks koolaid, so I feel a little guilty. Also did the same thing with separating every piece of code before I realized it was a fad...

Collapse
 
glaydsoncosta profile image
Glaydson Costa

Wow. That's really an epic post. I really like the way we used to do things in React, but today is frustrating the amount of "bullshit" you have to do to create a "pure React app", that's sucks.

Thanks Adam to write something honest and realistic about React ecosystem.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Thank you for the feedback! As you can tell from some of the other comments, some people are definitely not in agreement... But I'm fully aware that some people truly like all the ways that React has changed/evolved over time. I still truly love React - but I'm not always a fan(boy) of every new way that the current code morphs.

Collapse
 
glaydsoncosta profile image
Glaydson Costa

You're welcome Adam. I see how this post generated rants. That was the biggest reason why I left Twitter and stoped to following some "ReactJS rockstars" (it was too bullshit to me). Sometimes I get myself thinking when development became a religion and not a technical working, dogmatism took place over pragmatism, people do things because they "believe" is better, and because their "shepherds" said it was better, not because are really better.

Keep up the good work.

 
isaachagoel profile image
Isaac Hagoel

I agree. On the other hand, if everyone is sitting on the fence until a thing is mainstream, who exactly has the power to make anything mainstream? Are we waiting for the entry-level programmers of the world to pick the next thing because it is the easiest to use without knowing much? Are we waiting for facebook or google to tell us it's okay?

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis • Edited

To me, this is part of the conundrum of being a modern dev (and especially, a modern JavaScript dev). The beauty is that the language (and the massive universe of associated packages) is evolving at a breakneck pace. The downside to that evolution is that you can burn a lot of valuable time and effort trying to build something in a soon-to-be-dead fork of the evolution, merely because you placed your bet on the wrong technology.

I don't have a "proper" answer for this. But I was intrigued recently by a comment that I read from Ben Halpern on this platform:

"Inelegant software which has a huge following and a big ecosystem is often better than objectively cleaner, better software."

I wanna hate him for stating this (plainly obvious fact). On many levels, he's absolutely right. But it still sucks when you're convinced that you've found some package/solution/language/whatever that you swear is superior - but it just never gains a following.

Thread Thread
 
isaachagoel profile image
Isaac Hagoel

I keep agreeing with you. My question is: what is our (senior devs who have been around for awhile and could contribute code to any one of these frameworks) role in making something that we think is better mainstream?
A different way to phrase it: how does this process of evolution select for the winning frameworks and tools?

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

Some of this comes down to deeply personal choices about where/how you work and what's important to you as a dev. I'll freely admit that, for much of my professional life, I've been working for whomever could drop the most coin into my bank account. There's nothing wrong with that. But it does tend to leave you working for larger corporations. Big corporations so rarely spawn "green fields" work - it's all tied to big legacy projects. And those legacy codebases are rarely ever rewritten (nor should they be). So it's highly impractical to suggest switching-to/introducing any new tool/library/technology.

I really think that being on the "evolutionary edge" of technology requires a bit of a conscious choice on the part of the dev. So many (well-paying) jobs amount to "Add a new feature/module to this Big Hairy Legacy Codebase". So if you want to be in a place where you can actually have a role in making something mainstream, you'd have to consciously filter for that while you're making a job choice. Of course, many devs - even some very experienced and talented devs - don't feel like they can afford to weed out Big Corporate X that wants to throw money at them to crank out new Visual Basic modules...

Thread Thread
 
isaachagoel profile image
Isaac Hagoel

I see your point. My thinking is: even if I could make my team to select technology X, I don't think it will make that much impact. No one outside of my immediate vicinity gives a $#!t about what I think and probably they shouldn't. I didn't earn it.
I just wonder, if it is not people like us that make these things popular who is it then?
It is also indirectly related to another comment I wrote about dev culture:

Ah, I didn't know js files aren't compiled. I thought it processes them so that it can do its treeshaking magic and all.
On the one hand your points make a lot of sense to me and I can relate. On the other hand I wonder whether we (devs) became too spoiled for our own good. We seem to prioritise DX over UX and expect to be hand held (by our free tools and the communities that back them) every step of the way

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis • Edited

You can't really have "much impact" on the broader adoption of any given technology. For the most part, that's a good thing, because our individual tech obsessions are never quite as clear-cut and obvious as we'd like to think.

You can rarely do much to individually shape opinion, because programming is the tactical expression of a vast marketplace of ideas. Once any marketplace gets large enough, it's nearly impossible for any one person to really drive the market.

It'd be like if you woke up this morning with a revelation that Microsoft will be the hottest, most profitable stock for the next 10 years. You can throw all of your money into the stock. And if your intuition is correct, you'll eventually make a lot of money. But your bulk purchase of MS stock will have only an infinitesimal effect on the price. You can praise the stock to all your friends, but again, that's not gonna have any material impact on the stock, good-or-bad. In the end, "the market" as a whole will drive the long-term direction of the stock.

The only possible exception to this in tech is if you can (or even want to) find a way to become one of the tech's "thought leaders". If Dan Abramov wakes up tomorrow morning and decides that React is a stooopid name, and we should all be calling it "ButtJS" (pronounced "butt-joos"), then by golly, that's what most React fanboys are gonna start calling it. They'll even write deep think pieces about why ButtJS is such a better name. And they'll cover you in snark and scorn if you still insist on calling it "React".

Thread Thread
 
isaachagoel profile image
Isaac Hagoel

😂 you killed me.
You are right.
I still feel there is some "missing link" here... but maybe this feeling is wrong.

Collapse
 
gsonderby profile image
Gert Sønderby

Good post! I largely agree with you, though I may myself be part of the anti-class/pro-hook cults in some capacity.

I really dislike the class keyword in JavaScript. It makes something messy (prototype inheritance) look neat but doesn't actually fix any of the messiness. It misleads devs into thinking it's well defined.

The namespace job you describe I generally use files or even directories for - a typical complex component of mine might have a couple files containing comonents, one or two files with hooks, and an index file that brings it all together. It typically contains zero class keywords, excepting any test assuring compatibility with class components.

Functions, meanwhile. JS is a function programming language with some other stuff attached. I've treated it like that for fifteen years now, and it's been extremely compliant for that time. Function purity is a useful concept, especially for certain modes of reasoning, but I will agree it's overvalued. React hooks are quite honestly a massive godsend here, and I will not be moved on that subject.

But apart from these opinions of mine, I feel you speak truth and I agree.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

It makes something messy (prototype inheritance) look neat but doesn't actually fix any of the messiness. It misleads devs into thinking it's well defined.

Not trying to argue, but I'm legitimately curious. Can you outline any scenarios where this theoretical "problem" actually leads to tangible problems? Like, have you ever seen a bug that would have been avoided if the developer hadn't "thought it was well defined"?

Every time I hear a JS dev rail against classes, it always seems (to my ear) to be tied almost entirely to theory with almost no basis in tangible effects. Specifically, it also sounds (to me) like, "This is the way that I think about the language. And anything that allows others to conceptualize it in another way is bad."

Many of the NPM packages we use have some very "messy" stuff under the hood. But when we use that NPM package, it's typically pretty simple for us to call a single function or drop a single component into our render() and suddenly we don't have to think about all the "messy" stuff. But I've never heard an anti-class person follow up their objection by saying that we should banish NPM.

Even in the scope of a single application: I figure out how to do <SomeComplexMessyThing>. Once I've solved that riddle, I package it into a function or component that can be used by anyone else working in the app. They just drop <SomeComplexMessyThing> into their new code and... it works.

In theory, it'd be great if everyone understood all of the underlying "stuff" that was happening with the components/functions/packages/libraries that they were importing. But that's just not realistic.

And finally: This is... "good"?

const foo = {
   name : 'george',
   age : 42,
   sayHello : () => console.log('Hello');
};

But this is... "bad"???

class foo {
   name = 'george';
   age = 42;
   sayHello = () => console.log('Hello');
}

Again, I'm not trying to argue about anything. I'm just legitimately curious (and confused). After reading thousands of words about the "evil" class keyword on the internet, and hearing similar sentiments from other devs in-person, I'm still waiting to hear an empirical answer, from anyone, that explains why class is somehow "wrong".

Collapse
 
gsonderby profile image
Gert Sønderby

Actual bugs? Not on my own watch, no. But I have seen juniors with some extremely wrong ideas about how classes work in JS, which hampered them. The class keyword is syntactic sugar, as you know - by itself not a bad thing. However, it obscures things - and it makes people think in ways that cause them problems when easier solutions are available.

Your second example isn't bad - but for proper equivalence the first one would have to look like this:

function foo() {
  this.name = "george";
  this.age = 42;
  this.sayHello = () => console.log("Hello")
}

Then you can new either one and get an instance.

Now what happens when Fred the Junior goes const bar = new foo(); bar.sayHello = () => console.log('Begone'); bar.sayHello();? Probably what he expected. But then Andy the Junior goes delete bar.sayHello(); because he needs that to not be present, and... Well, that didn't go to plan.

Consider this instead:

const foo = () => ({
  name: "george";
  age: 42;
  sayHello: () => console.log("Hello")
});

A simple factory function, same outcome as your top example except you can get more of them. Results are a lot easier to reason about, what you see is what you get, there's no secret machinery in the engine.

I'm just not aware of any advantage of classes in JS that makes them, their pitfalls, and the wrong ideas they spawn, worth it.

Meanwhile, my last two years were spent building exactly the kinds of abstractions you speak of, and as I mentioned, no classes in there. None needed.

Collapse
 
michi profile image
Michael Z

I very much agree with a lot of these points. Redux solves a problem by creating a new one, which leads to another level of abstraction, which in return creates a problem that needs yet another layer of abstraction. There is even a reduce reducers library. Reminds me of joelonsoftware.com/2001/04/21/dont...

But, MVC is dead...? Guess what this very platform is running on ;)

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Haha, I hadn't seen that post before, but I love it. "Architecture Astronauts". I'm gonna be using that one. And to think that he wrote that nearly 20 years ago...

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

I would never say that MVC is dead. But 10-12 years ago, it was definitely a full-blown fad. It was one of those catch phrases that devs just spewed to make them sound more knowledgeable.

Collapse
 
louiszen profile image
Louiszen • Edited

MVC implemented in PHP or .NET is dead because their MVC concept is totally wrong. They still wanted to keep everything in a compiled project but only separate concern in folders, not thinking out of the box that they can actually have 3 projects for M V C.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Thank you! The ironic thing is that now I'm doing almost all of my development in Hooks. But not because I "saw the light". It was basically a practical decision based on the teams/projects in which I'm currently working. I've already written a few articles that explain some of the benefits (and problems) that I've found with Hooks. I'll probably be doing a few more where I outline some of the stuff that I genuinely enjoy about them. But overall, I doubt that anyone can really convince me that classes are "bad" or "wrong". At worst, they're just "different" from functions/Hooks.

Collapse
 
louiszen profile image
Louiszen

Just a syntax perference.

Collapse
 
lelandkwong profile image
Leland Kwong • Edited

One of the biggest drawbacks to localizing state in a class is lack of composability. With hooks you can easily share data across components, which becomes incredibly valuable when it involves async requests or state that needs to be accessed between multiple components.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis • Edited
  1. I almost always have the constructor in my own apps because there's almost always a few other things happening there, so I've gotten too-much in the habit of starting with the constructor "by default". But you're right. It's doing nothing here. So I've removed it.

  2. I just re-jiggered it so that saveCounter() is called first, as a side-effect of the existing state.counter value.

  3. I agree with Michi on this one. Removing the prevState modifier removes the explicit indication that we're updating the current state in relation to previous state. And it's "hardly readable" without a space after the colon?? Meh. Poe-tay-toe / poe-tah-toe. Now we're getting into tabs-vs-spaces territory...

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

I sincerely appreciate the feedback.

Collapse
 
keskyle17 profile image
Chris Briggs

I'm an old fart, so I still use elements of UML to justify development strategies. In the end that's what it comes down to: justification and rationalisation. For beginners in React (including me), your example might look very familiar, and logical in its form. For MVC to be a paradigm it needs to scale appropriately, from single-page to enterprise with common sense in between.

Collapse
 
michi profile image
Michael Z

I don't think that last example makes it more readable. Not everything needs to be destructured. prevState is very explicit and that's a good thing imo

Collapse
 
mrkfdr profile image
MarkFdr

been running in my had for a while how to describe the mess happens in react today.
excellent paper, thanks for writing it :)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.