DEV Community

loading...
Cover image for React Is Eating Itself

React Is Eating Itself

Adam Nathaniel Davis on March 09, 2020

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 reas...
pic
Editor guide
Collapse
bytebodger profile image
Adam Nathaniel Davis Author

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
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 Author

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 Author • 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 Author • 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 Author

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 Author • 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".

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 Author • 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 Author

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 Author • 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
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 Author

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.

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 Author

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 Author

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
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 Author

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
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 Author • 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 Author

I sincerely appreciate the feedback.

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 :)

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
httpjunkie profile image
Eric Bishard

React is beast urz a sucks man.

Collapse
glaydsoncosta profile image
Glaydson Costa

hahahahah sad

Collapse
bytebodger profile image
Adam Nathaniel Davis Author

I wish I could give this more hearts.

Collapse
glaydsoncosta profile image
Glaydson Costa

hahahahah good!