I stopped guys, I stopped doing it. For internal tools at my company, I just use a golang backend (because most of our services are golang so there's no learning curve for folks) and use go's templating system to render HTML.
I can crank out tools in minutes. No complicated build systems or web pack or dependency management system. No react, no reduce, no apollo or graphql. No typescript, etc.
Just simple go, html, css, and a bit of javascript when it's needed for a form. I don't minimize anything, or try to do anything fancy. It faster to develop in, and faster to load in the browser.
I'm specifically talking about internal tools here.
The usual problem is that people often pick an SPA tool for a job that a CGI script can handle. The fashion made "web UI" and "react" associated too strongly.
Much simpler tools can work if you allow for less fluid interfaces, and this may be a non-issues for a tool running on a LAN.
We're never replacing our old-style PHP internal tools, because according to business users it's "instantaneous", and any attempts to use a more SPA approach cause complaints about "sluggishness".
Once the CGI page loads, all info is in there. React requires 1 request for the basic HTML, (1 request for the JS but it’s generally cached) and 1 request for the data from the REST API. It’s double the time to load a page.
React apps constantly have gray placeholders waiting for data to finish loading. Contrary to initial experiments, progressive rendering is recorded by the brain as a bad experience.
React is simply a library that renders your UI to the DOM. It doesn’t have any opinions on how your data is fetched, or how many roundtrips it takes. That’s an orthogonal concern. People have been using XMLHTTPRequest with CGI since the 00s, way before React was a twinkle in Zuck’s retinal sensors.
You can send your initial data for React with the HTML response if you want to eliminate the extra round trip on startup. Depending on the geographical distribution of your users it may or may not be worth it.
React's new server components (only in next 13 at the moment, coming soon to other framework) addresses this problem. Of course react and node both have performance work they need to do also.
I have just spent the last year doing the literal opposite.
15 year old software product built on Perl CGI with a server rendered html+jQuery front end. Massively grown past the point where that is suitable, with plans to add interactive features that would be crazy to do with the old architecture. Have moved the whole thing to a VueJS frontend. We have kept the old Perl CGI server architecture, just refactored it to return JSON.
You are complete right, to many people chose a tool based on fashion. There are two main things to consider when picking a toolkit or architecture for a web app/site:
- what do my team and myself know well, what will be the most efficient?
- where is the state? Is it predominantly in a database on the server or client side in the browser?
The project I'm on when they first asked those questions, the answer was, Perl CGI and server side state in a database. That is no longer the case for the second question, the state is increasing moving to the client side, and that's why I joined the team.
If I was answering the second question as server side right now I would probably pick htmx+alpinejs.
However, sometimes the first question overrules the second.
(There is a third question, do you need an installable app too? In which case a SPA with a toolkit that supports PWAs or web view wrappers is a good option if you want to only have a single codebase)
I mean, there’s been outcries how the US is overly car-dependent and driving should be a choice not a requirement etc. The parent is onto something if you go with this analogy.
I’m not sure the complex problems these tools were originally created for still exist. For example, do you really need to support old browsers? If not, you probably don’t need a transpiler anymore.
Well, webpack has been replaced by vite (for those who pay attention to front-end trendfuckery) which is much simpler than Webpack and Babel is eliminated.
We are still not quite at the point where it’s practical to not have a module bundler. Handling things like imports etc is still necessary.
But we are moving away from React. Things are slowly going back to sanity. SvelteKit is an excellent step in this direction IMO.
Their customer support pages with built-in live chat do. And I'm assuming we're excluding aws.amazon.com with realtime graphs and viewable log streams.
Take a look at htmx (https://htmx.org/) if you haven't already. For my use cases, generally internal tooling like you mentioned, I've found it integrates extremely well with go templates and more often than not provides all the functionality that I'd generally be reaching to javascript for.
99% of us live in a world that does not need reactivity, yet so many of us are led to believe that we are in a world that does. All because Facebook made React (for what original use case? Facebook Ads Manager?) doesn't mean we're all going to be Facebook, need to be Facebook, or want to be Facebook.
When I need the reactivity, I need to sprinkle it on; I don't need to use reactivity when it's not needed just for the few cases that do.
I mean, you're welcome to go back to PHP and jquery like it's the dark ages, but from where I'm coding, react is the only sane way to deal with a big complicated app.
You have no idea how bad it can get. I hate hate hate anything Ruby. Rails itself is a good idea, but you basically get 0 editor support even with tooling and you barely know the structure of your objects.
If you stay away from metaprogramming in your own code Rubymine brings a lot to the table:
Go to definition (all the way down to any source code in gems)
Usages
Refactoring (renaming methods, classes and filenames)
Integrated debugger
It even infers types now
Brings nice documentation for methods if you use RBS and/or Yard
I think this goes as long as it can be taking into consideration that Ruby is dynamically types and has a lot of support for meta programming.
I know of Rubymine and use it daily. Rubymine sadly gives up if files get too large, which is realistic for `config.rb` even in medium sized Rails projects.
But I wonder if a language can be considered good for use if you can only really use it within a dedicated, non-free IDE.
Heh, this comment made me giggle. Indeed when someone says that React (or any other technology to write a SPA) sucks, they should just go back to PHP + jQuery in order to remember why we are here.
> Just simple go, html, css, and a bit of javascript when it's needed for a form
I mean if your tools are that simple, this is a no-brainer. The problem is when you have to scale up. Internal tools can get complicated because their user base is often highly specialized technical users. Managing reusable components, behavior, and state of a complex application is when you need something like React.
I’ve grown to loathe React but your approach breaks down horribly once you start getting to a certain level of complexity. There is a reason frontend JS frameworks exist, I just wish the default wasn’t React.
Have you tried Vue? I work for a huge company (nearly 100,000 employees). We tested both React and Vue and settled on Vue. That was years ago, but we’ve had no regrets since.
I've used Vue. It's much worse than React. It's probably ok for very small sites but its automatic reactivity system seems almost deliberately designed to produce spaghetti code and subtle bugs. Plus it has poor Typescript support. You don't have to worry at all about type errors in TSX.
That said, I'm liking Yew a lot more than React. The difficult thing with React is state management, which is why it's common to resort to immutable state (even though that's a real pain in JS), and even then React chickens out by assuming components are impure by default.
Everything makes way more sense in Rust. The big downside is that Yew doesn't really have anything like Bootstrap or Blueprint yet. I mean there are ports like Yewprint (great name) but they seem to be complicated to set up and you lose the really nice simplicity of Trunk.
It won’t be. React had its time and is now waning. The future is frameworks with no virtual DOM that are interoperable with other JS frameworks like Svelte.
You can still throw in something like Vue later, easily. It is one script tag away. That is what most do not get. You can go the traditional template rendering way for as long as it serves you well and then you can still have interactive components, when you really need them.
I was recently writing raw html and the typesystem compared to js components (aka JSX) is... just not there? I hate coding without proper autocomplete.
Yep. My experience has been that you can start with a wonderfully simple tool, but you end up jamming in dynamic behavior because users today want it and expect it... even in "simple" internal LOB apps.
Updating the UI in response to user value input and state changes in real time: error messaging, validation, presenting more relevant additional inputs, formatting, etc.
How much of a usability win is making things “real time?” Submitting a form and having the server do validation and render the error messages can feel just as fast. Plus, if you use the correct HTML input types the browser will handle the most obvious validation without any JS.
You have to do validation on the server anyway. Why do it again on the front end?
You don’t have to do all validation on the server side.
Did the user forget to fill a mandatory field? Is this phone number actually even possible? Does this zip/postal code exist? Is the age entered too low to create an account? Is the password long enough? Does it meet all the requirements. Can we dynamically show which requirements it doesn’t meet.
Even email, which you will want to verify by actually sending an email, can have some basic front end checks (is there an @ in the email entered by the user).
In fact, I bet the vast majority of validations can in fact be done in the front end in real time.
Client-side code is subject to potential manipulation. No validation you perform there can be a guaranteed truth for the server-side. Thus you cannot replace validation on server-side with validation on client-side.
You had all this (most often as free & open source jQuery plugins) 10 years ago.
Honestly, 99% of webapps today are 2 very interactive pages (which could often be developed in jQuery anyway) + bunch of generic datagrids & forms we have seen many times since the introduction of <form> and <table> tags decades ago.
While this could be achieved on the frontend with jQuery + plugins (or even vanilla JS, let's be honest) I think you're not remembering how cumbersome and error-prone building forms / complicated flows was without a dedicated framework. Speaking from experience, I believe Backbone.js was the first time I felt confident building state based UI on the web. Newer frameworks (first Angular, then React/Vue since then) have upped the stakes.
Honestly, my problems as a developer generally aren't with the frameworks (I'm mostly defending Vue here, as I find it predictable and easy) but with the build tools, which have gotten a bit out of hand and always feel brittle.
Personally, I think the main problem with jQuery was .data instead of having proper state management. If we used jQuery just as a library and not the centre of everything without any layer above, we might be perfectly ok with it even now in 2023 for everything that's 100* simpler than GMail (which most of webapps are).
Verbosity is hardly the problem these UI libraries/frameworks solve. They establish idioms for data flow and composition.
The UI component model’s data flow might vary between libraries, but they’re all generally opinionated and fairly consistent internally. The equivalent in “vanilla JS” can easily become unwieldy even with a lot of discipline, because the underlying APIs are designed with the view as the source of truth. That’s almost always the opposite of how you’d want to design a system with more than a very small amount of complexity. But that’s where inertia will guide you because there isn’t even a notion of state that doesn’t reference the DOM etc.
Once you’ve succumbed to this even partially, composition becomes incredibly difficult because the underlying model leaks implementation details. Tangling them is not just easy, it’s the default. Avoiding tangling them means basically developing a UI library with its own opinions and idioms. Which maybe you want that, but most people are building something else.
Untangling them is an enormous task. I’m maintaining an application that began in the era when this was common, and picked up some notion of components along the way. Even just understanding how things get invoked can take hours or days tracing app code and its interaction with implicit state in the view, and that’s with two years of familiarity with the code.
I don’t even like React or several of its idioms. But stuff that takes me hours or days would take minutes to understand with just the basic assumptions React or any other component library would afford. I can’t speak for any specific commenter, but I think this is the kind of “scale” most UI devs mean when they discuss it in those terms.
And granted, the critique that many things don’t need the scale they take on—that they add incidental complexity to fit a library/framework’s model. That can certainly be true, but I think the “vanilla JS” camp really underestimates how steep that cliff is and how quickly many projects can run off it.
Back then, people fell in love with GMail and suddenly everyone thought they were building another GMail. This wasn't true. Most people were building an app with a couple of forms, a datagrid and maybe 2 highly interactive pages.
PHP, Python & Ruby frameworks gave perfect support for all this >10 years ago. You had jQuery plugins you added to page in 30 minutes and you had validation. In 2 hours you had a datagrid. Autocomplete, datepicker, things like this was no problem.
It was a form of egoism all the time.
Coding Sign In page was work for less than an hour. Now, in React, it's 2 days.
Look at developers now and compare them with senior devs a decade ago. My estimate is that development today is 4 times slower. Given a growth of salary at least 50%, a system back then in MySQL, jQuery, Django or Zend Framework for $40,000 is now $240,000 in React with GraphQL, Mongo, etc.
It's my observation as someone who leads developers well over a decade.
Development just got way more expensive, slower to get app-like feel. All this when >80% of pages in almost any webapp could be server-side rendered without big (or any) sacrifice of user experience.
It's so tiresome. I hate Go but you don't see me bringing it up every time someone mentions it. For some reason it's irresistible, I guess because they know the choir will like the sermon.
You can actually measure the amount of dollars a language / framework / tools generates in real business applications by looking at how hated it is on HN.
Removing complexity where you can is a good thing. Being able to recognize when you don't need a complex system is a skill. Many devs fall into the "One tool for every problem" trap by defaulting to the same boilerplate project for everything (eg create-React-app or Next.JS) and end up making something complex when a much simpler system would have worked better.
If you have a basic project the tools should be basic.
Your tools should be as complex as they need to be. For most apps, that is a lot simpler than the developer has made them.
The inverse isn't true because as soon as you actually need complexity your basic tools stop working. The "smug superiority" of the original post here is an admission that the author didn't need complex tools. The fact they recongized that, even if it was for the wrong reason if you're right about the smugness, is a good thing for their users.
So now that you've said React is indeed useful for complex projects, what relevance does the original post have here? It does nothing except clog up the comments with people tooting their own horns about how amazing they are for not using it.
When every new hire runs "npx create-react-app" without thinking as the first step of any solution to any problem, avoiding unnecessary complexity because you don't chase fads seems like a superpower.
There are times where React/Vue/SPAs are warranted. It's not 100% of the time, however.
Small "unless" and you are forced to make all your APIs generic because there might be many clients.
Small "unless" and you'll ignore all power features of your database and dumb-down your approach to data because user might want to switch a database.
Small "unless" and you will make your app ultrascalable even when you have no proof there'll ever be more than 100 users.
This is a disease that makes development slower and more expensive even when 95% of time, the customer needed something fast and cheap. No, most devs aren't building another GMail, most devs are building something that could have been modelled in 2 weeks in Visual FoxPro 25 years ago.
Well, it does require some though about SPA or website upfront, but will save lots of work and time to being online. I guess we can at least demand that much thought about things, instead of jumping immediately to SPA and causing weeks and months of more work.
I have seen it. It is all "But React has this built in!" and "React does this nicely!" until it comes to actually implementing components. Without a need for interactive widgets to avoid page reloads and avoiding becoming a non-SPA again, it will take a lot more time to get things done with React, that it would take to simply churn out server-side rendered templates the traditional way. UIs that could have been done in 2 weeks the traditional way take suddenly multiple frontend devs a few months, with things like the back button not working correctly.
Specifically for internal tools, this is a much efficient way.
With anything more than html, css the complexity is not worth the return.
We've done away with everything that needs internet to build or deploy (npm, dockerhub, brew, whatever). Primarily for the security theatre around package updates.
So much simpler now. Backend is very well integrated with the entire ecosystem. Exact same setup as production. Folks who build internal admin tools tend to (and they should) know that part much better.
Everything is instantaneous.
We avoided all frontend libs. Some (like moment.js) needed extra work on the API side, but that took an hour at best to find an existing backend method to use.
All of the admin UI stuff is like < 100 kb. In 1 repo. Everything works without javascript, in all browsers (including lynx).
Night and day contrast against all previous versions where we used a mix of jquery, then angular and finally react with all their baggage and unexplainable dependencies to utter dismay.
Clearly the case of using the wrong tools but good lord did it take a while to get rid of the FUD around html only tools.
Ironically I opened this up hoping to see the first comment was "just don't use React" and was even more surprised that it is exactly that and also what I've been doing for a while.
I also wrote a < 0.5Kb DOM abstraction that basically just saves a bunch of typing (get element, replace element with something, go fetch some shit off the server, add style, remove style) and that covers about 90% of the "would be nice if it did this" use cases.
This all gets compiled into one binary file, resources as well, chucked in a docker container and pushed to Kubernetes.
Makes sense, you were creating static pages but were using a tool that is dramatically overengineered for that use case.
I would've switched to Astro for that use case, mainly because you could still leverage your understanding of React/Vue/Svelte/whatever within those mostly static pages very easily, plus the markdown support is brilliant for internal tooling.
Why not use something like Retool? IMO if you’re building internal tools from scratch like that there’s a 99% chance you’re spending too much time on them.
Yes! Thank heavens people are finally starting to see the light. All these nonsense “modern” frameworks with their broken layers of abstraction need to read that article by Joel spoelsky on leaky abstractions.
And don’t be afraid to use this approach outside internal tools.
This is how most of the FrontendMasters.com website is made. Go/Hugo templates and vanilla JavaScript. People are always shocked at how fast our website is. Write the code necessary for the job at hand.
No? External changes to variables trigger reloads too, it was quite a nice experience coming from back end and hobby game dev.
Now, the reactive blocks, where an update to _any_ variable used in them trigger the re-evaluation seemed rife for abuse - particularly when the variables could be brought in from other files, but I forget the mechanics behind that.
I used it plenty of course. Forgot how it worked the day after I stopped working on the project though.
Edit: unless you mean lines like `ultrasonicDetectedDistance.update(v => v = distance)`, which yeah was an interesting quirk. Not enough of a pain to go with something like React, thought that wouldn't meet the latency requirements for this project. TBF neither did Svelte entirely, but it got close.
You're using Go's build system, dependency management system, templating system (a transpiler...), etc. You're just replacing one "evil" by another "evil".
On mobile (safari) it doesn't show results as you type. You have to type your partial string and then press the dropdown selector to see the list filtered by your string.
Sure, so if you need more functionality or different styling they won't work. But I suspect many devs reach for a JS searchable dropdown replacement without even bothering to ask the question of whether they really need something else.
Okay. I'll explain it to you. It basically comes down to a server-client architecture. The server (db, server side code) is the backend, that the user never gets to see. The client (basically the browser - js, html and css) is what the user get's to see, not just through the interface, but by selecting "view source".
So PHP and Rails are back-end languages. You never get to see the code and logic that generated the page because it is "at the back". Golang, which the poster mentioned, is also a back-end language. So if the core of your apps logic is at the back-end (server), what you are practicing is back-end dev. If the core is in the front (e.g. SPAs) then it's front-end dev. Sometimes the complexity can be split 50-50 between the back and front. But if your web-application just uses html, css and a sprinkling of JS, then it is back-end driven. Another category is a website, which does not really have any complexity whether at the back or front.
So to correct your statement, PHP and RoR have never, at any point in time been considered front-end tech. If it is not html, css and js (including complile to js languages like ts), then it is not front-end dev.
I'm not sure how long you've been in the industry. But you sound like it is less than maybe 10 years. Before react and such, you were considered a frontend developer when you delivered your javascript + html + css from any backend, though typically it would be served by php, RoR, or, more limitedly, by some python. Backend developers didn't touch html or css and couldn't be bothered to to think about js. The balance between full stack, backend, and frontend teetered on if you we're concerned with pixel alignment and UI things vs data storage, manipulation, and retrieval.
Again, back in the day, you wouldn't call yourself a backend dev if you did anything meaningful with html UI. You would still call yourself frontend even though you had to work through templating in a given backend language.
Source: I've been doing this for a couple of decades.
> I'm not sure how long you've been in the industry. But you sound like it is less than maybe 10 years.
Okay. No offence, but so do you.
> Before react and such, you were considered a frontend developer when you delivered your javascript + html + css from any backend, though typically it would be served by php, RoR, or, more limitedly, by some python.
Not really. Back in the day, before the great divide [1], the terms we used were "Web Designer" and "Web Developer". We also had "Flash Developer" but I digress. A web designer was expected to know all the front-end stuff including JS, since the front-end was not that complex. Almost all complexity was at the back-end, and that was the job of a web developer. The term "front-end web developer" simply did not exist in 2003. There was nothing to "develop" in the front end, since all complexity was on the back. Yes, there were some exceptions, but in those days, PHP was king. React is current king, and it's funny how it gets the same hate PHP got back in the day. Some people just like to tear down and burn whatever is at the top. Reacts successor will get the same hate. Thats how you know who the king is.
> Again, back in the day, you wouldn't call yourself a backend dev if you did anything meaningful with html UI. You would still call yourself frontend even though you had to work through templating in a given backend language.
Wrong. Why would someone working on PHP and MySQL call themselves frontend? So who were the backends back then? Strange thing to call yourself "front", when no one called themselves "back", don't your think? Like I said, the
terms back then were web developer and web designer.
> Source: I've been doing this for a couple of decades.
> PHP and RoR have never, at any point in time been considered front-end tech.
Okay, let me explain something to you, as a person who has been in this biz for 23 years.
What you're talking about is client-side vs. server-side which doesn't have a 1-to-1 correspondence with frontend vs. backend.
Frontend has always been about something user-facing. Backend has always been about something that is non-user-facing.
Client-side dev has hijacked the term frontend to describe purely client-side development. Even though with the exception of direct DOM manipulation there are very few conceptual differences between generating UI on the client and generating the UI via PHP/RoR.
> Okay, let me explain something to you, as a person who has been in this biz for 23 years.
Since you've brought up your experience, what exactly does "in this biz" mean exactly? What was your job title 20 years ago? 15 years ago? 10 years ago? 5 years ago? Now? If you've really
been in the industry that long, you should have noticed that the job titles also evolve. Back when all the complexity was in the server, and JS was just a baby toy language and not the
beast its evolved into today, the job titles were very different than they are now.
> What you're talking about id client-side vs. server-side which doesn't have a 1-to-1 correspondence with frontend vs. backend.
Just google "front-end" dev, or even use wikipedia [1]. The client-server architecture even pre-dates the web, but when it comes to web developement, the front end is css, html and js.
Nothing more, nothing less. If you do not agree, then go edit that wikipedia page.
> Frontend has always been about something user-facing. Backend has always been about something that is non-user-facing.
What do you mean by "user facing"? Can a user view the source code of a PHP generated page? How is PHP user facing? Can you give me an example of a web tech that is "non-user-facing"?
> Client-side dev has hijacked the term frontend to describe purely client-side development. Even though with the exception of direct DOM manipulation there are very few conceptual differences between generating UI on the client and generating the UI via PHP/RoR.
The term client-side dev is never used in the industry. Never. What we use is front-end developer. Go to any job ad website[2] and look for the term "client side dev", you wont find it. What you will find is "front-end dev". Then look through all the front-end dev job postings, and show me one, even one that lists PHP, RoR or Golang as a job requirement. Here are some front-end roles [3][4]. Notice how none of them mention PHP, Golang or RoR?
Then here are some back-end roles [5][6], notice how there is no mention of js, html or css?
> What do you mean by "user facing"? Can a user view the source code of a PHP generated page? How is PHP user facing?
It literally produces the website that the user is looking at.
Unlike, say, a microservice that retrieves some data.
> The term client-side dev is never used in the industry. Never.
If you paid attention to what I write you could've seen this: "Client-side dev has hijacked the term frontend to describe purely client-side development. ".
This is what happened, and you are a great example of this.
Two technologies produce user-facing UIs and sites by stringing together data from different services and presenting that to the user.
"OMG PHP runs in the server this is backend unlike this JS code that literally does the same"
> It literally produces the website that the user is looking at.
Your definition is meaningless because EVERYTHING in the pipeline literally produces everything "you are looking at" from the database to the css.
> Unlike, say, a micro-service that retrieves some data.
That data still produces part of "what you are looking at". What a useless phrase. Stick to industry definitions, yours do not make sense. And there is no language called "micro-service". I asked you to name the so called "non-user-facing" part of the web development stack.
> If you paid attention to what I write you could've seen this: "Client-side dev has hijacked the term frontend to describe purely client-side development. ".
So, old man yells at the cloud and uses his own idiosyncratic terms. What matters is not who hijacked what. What matters is that the industry has settled on the term "front-end developer". Complain as much as you want, but realize that train left the station. Go ahead and use your own terms that no one understands because "my 23 years experience", but that's exactly how people fall out of touch. And then when you say thinks like "PHP is front-end dev" people will immediately dismiss your knowledge, so you have to keep reminding them "but look.. my experience!" The term is "front-end dev", deal with it.
> "OMG PHP runs in the server this is backend unlike this JS code that literally does the same"
Old man yells at a language that can be used both in the cloud and browser.
When JS is executed in the server (e.g. Nodejs) is it part of the back-end stack. When it is executed by the browser, it is part of the front-end stack. When it executes on both, it is full-stack. Kapish?
It's simple enough: before the rise of front-end frameworks in the 2010s, web developers were indeed more of a single group ("full stack"). But even before the rise of Angular in 2011/12ish, there was already starting to be a specialisation in front-end vs back-end; some devs, I remember, worked almost wholly on the JS UIs of more front-end-heavy websites (they may have been more of web designers previously). Others concerned themselves more with the backend, databases, services, etc. As frontend technologies grew more complex (and mobile became of primary importance), the division grew.
Before Angular and co., "full stack" encompassed using HTML, CSS, and JS. What it can be fairly said to mean nowadays is debatable, but 10+ years ago, doing Rails with a bit of JS would definitely have counted.
The key skill that you're looking to pick up is actually what professionals think of as "full stack web development". That is, you should aim to understand lots of things:
- MVC web frameworks like Rails and micro-frameworks like Sinatra
- MySQL and non-relational datastores like MongoDB
- web and proxy servers like thin and nginx
- Redis! it's like a Swiss Army knife... but also Memcached
- jQuery and Haml/Sass
- Backbone and websockets
====
Basically, in 2010, if you knew how to work with jQuery, Haml/Sass and maybe Backbone (in addition to backend tech), you could be called "full stack".
And that Sep 2010 Who's Hiring post had many ads looking for front-end (or UI) and back-end engineers, meaning that it was common to be specialised at the time.
Looking back, wow, the experience/knowledge requirements back then were pretty low compared to today...
LOL! Looks like some people are silently getting butt-hurt over a simple challenge asking them to differentiate between front-end and back-end dev. If front-end dev is not your cup of tea, that's ok. Just be honest with yourself about it. Using only html, css and a sprinkling of js is not front-end dev, it is web-design.
If your output is HTML and CSS, you are full-stack dev. There are frontend-heavy full-stack devs ignoring backend with Firebase instead of proper backend. Nobody is telling them they aren't full-stack. So let's stop calling developers backend when they provide backend-heavy solution with a bunch of jQuery plugins or HTMX app.
> No react, no reduce, no apollo or graphql. No typescript, etc.
All of these bring 10 benefit but add 12 problems back. It makes developing project slow at start, slow at medium term, and super garbage at long term. If developers get paid $100k+, I think they should be able to write good codes and have a good team instead of using these to save them from mediocrity.
If you have to really do SPA, pick something that's not bloat, not slow, not flaw .. instead of "hiring pool" BS.
I'd like to add improper use of useEffect to this list. useEffect is an escape hatch but I often see devs using it as some kind of ad-hoc event driven system approach. useEffect divorces the outcome of an action from its invocation which is problematic. It can lead to some tricky state management scenarios really quickly.
And as a side note: can we *please* stop shitting on every React post that appears on HN? All this hater energy is really starting to be a drag.
I would say useEffect is not an escape hatch, it's a fundamental building block of the hooks paradigm. You need some way to run impure side effects after your component has been rendered, and `useEffect` is how you register those effects.
The most basic example is making an external request based on props: can't do that in the render loop because it has to be pure, since React may run it multiple times before committing to the DOM. `useEffect` is the only way you can get a guarantee that it will only be run once after rendering, and only when its dependencies change.
This is definitely true, but I also think it's overused in typical UI components. I often see patterns where some user event triggers a state change, which triggers a useEffect hook. In most cases, you could instead have your event handler directly trigger your side-effect code. Adding useEffect into the mix is a huge point of failure because, well, it sucks. Until you get that chain of deps just right it's not going to run when you think it is, or it's going to run with some stale values.
That's funny, they're such a necessary tool for making anything more than simple UI's that I wouldn't consider that an escape hatch. Kind of like saying that screwdrivers are an "escape hatch" for hammers.
Which brings us back to the biggest problem with useEffect. Even the react devs don’t seem to have a clue how it should actually used, until only recently, despite it being among the 2 most used hooks ever since hooks were introduced.
To make it concrete, I think they're saying, if state "rows" depends on state "filter" and "sortOrder", people write `useEffect(() =>setRows(...), [filter, sortOrder])`.
Ie writing reactive code but relying on the React render loop to trigger reactions. The problem is that you can no longer step through your code in full and as you say, your code now mandates flushes to the DOM that are unnecessary (mid computation).
It's better to setRows at explicit places or learn and use a library like rxjs if you really need to encapsulate that complexity.
Sorry, what do you mean by mutable vs immutable code? I don't think it necessarily has to do with mutability, because you can "mutate state" in regular callbacks without useEffect. The only way to get true mutability in React is via refs, all other state is immutable.
> you can "mutate state" in regular callbacks without useEffect
Yes, via `useState`. That too gets overused. (Note: useEffect and useState are 100% necessary, but also easily overused.)
useEffect for when you want to manipulate state outside the component. I.e. change the document title, exchange data with an HTTP server, store data in localStorage.
Also: even with hooks, the component is still a pure function in a less low-level kind of way.
The hooks yield effect descriptions, to be carried out later, on a side channel. So the component is still a pure map of the inputs to the outputs, just that the outputs are not just comprised of the javascript function's return value, but also the effects on the side channel.
You really don't need to give hooks access to some global variables (or all of them). You can, of course. But you would actually do that in a useEffect or use some setState in a callback somewhere.
It's a cheap shot to complain that Javascript isn't a purely functional language... React can be used in a pretty pure way and if you don't you'll don't get the full benefit.
That’s not pure anymore for any practical definition.
When you get different answers for the same question, then you’re not calling a function.
When you can only get the same result by recreating the same internal state through external manipulation, you’re dealing with side effectful, imperative code.
Hooks are ergonomic and easy to reason about and that’s great. But they turn functions into objects.
What you’re missing here is that the hooks form part of the input to the hook/render function. There’s a reason they can’t be conditional (though with a compiler theoretically they could be).
The whole idea of the useState hook, is that it’s _not_ internal state to the hook. That state isn’t stored on the stack of the hook function, but against the component.
I understand that, but your component is now an object that can be mutated via events and it tracks internal state via calls to useState.
You cannot call the component with the same arguments getting the same results anymore AKA it’s not pure. You indirectly mutate it via event handlers, which are effectively methods.
no, it can't be mutated by events. The hooks only react to inputs in the side channel and only during execution.
If you attach an event handler, that "attaching" is an effect and executed outside the rendering. If the handler changes some state of the context, a rerender is triggered.
We're either talking about two different things or you are missing the forest in the trees.
The value proposition of FP doesn't come from whether you feel like you're doing FP during implementation. It can only be assessed from the perspective of the caller. The caller doesn't care about the philosophical differences of how you achieve internal mutation or the implementation thereof.
They only care about whether you are returning the same result when they give you the same arguments. That's how you satisfy a function interface.
A component that keeps track of internal state with useState and modifies it via event handlers does not satisfy a functional interface, because given the same props, it may or may not return the same docoument fragment.
One cannot pass in the same state, nor can one see the state from the return value. The signature of a component that uses useState and event handlers to modify it, _hides_ internal state from the caller. A component like that doesn't satisfy a functional interface but does in fact hide an object interface via local retention and message passing.
Vice versa, a component that bangs on an internal variable in order to derive/calculate values that it returns, can satisfy a functional interface from the caller's perspective. Similarly you can use useState and useEffect to implement a functional interface. It all comes down to whether you are giving the same answer for the same question, which is typically not what you're doing with hooks.
Note that I'm not making a value judgement about whether a component is a function or an object (via hooks). If you need an object, then write an object. But be aware of the tradeoffs of writing a function vs an object and be upfront about it to your caller.
It's really just a question of definition and Perspective. From the perspective of the JIT or Assembly, there's no such thing as FP. So you got to make abstractions.
And a very helpful abstractions is to say that the context that you are saying is impure is just another parameter and another part of the return value. Because that's how React is treating context, and that's why you can treat React components as functionally pure if you respect the rules of hooks.
FP doesn't demand the parameters stay the same. It also allows for outputs of a function to get put back in as a parameter in a subsequent invocation, which is what happens to the context. If you insist on seeing it another way, I can't stop you, it's just more complicated and I don't see how that's helping.
> FP doesn't demand the parameters stay the same. It also allows for outputs of a function to get put back in as a parameter in a subsequent invocation, which is what happens to the context.
My point is that _you as the caller_ are _not_ doing this when calling a stateful component Foo. Foo mutates between events and state is entirely encapsulated, so you are getting different results from the same question. The caller doesn't care about the internal execution model of Foo and React. A functional interface is _only_ satisfied if the function is referentially transparent. Which in this case it is not.
If all you can do is send messages and observe behavior from outside. It behaves exactly like an Object and not like a Function from the perspective of the caller.
---
You are arguing in terms of the implementer of a stateful component. You are thinking in terms of how React/useState behaves from inside your component and further up the call stack. And in this context I agree. You can think of useState as part of your signature so to speak. In fact the hooks rules prevent you from doing things that violate this mental model.
But that's not what I'm talking about. I'm talking about the practical, real world perspective of a caller and whether they observe functional or object oriented behavior from your component.
> hooks form part of the input to the hook/render function.
They are input, but they are not input parameters.
A function is pure if it returns same result for same input parameters. Hooks make it possible to return different result for same input parameters, therefore making the function impure.
Again, no. Functional components are not defined by just their "javascript function" but rather by the contract with the rest of the rendering system.
And that's why the context manipulated by hooks looks like a sideeffect, and it often does describe sideeffects, but it doesn't. That context is both an input and an output, only the rendering logic in React decides what to do with that output. Without the rendering logic deciding to execute those sideeffect, the context isn't actually changed at all. Which is why you usually don't need to care about when or how often the component is executed.
The whole point of pure function is that the caller can understand its dependencies without looking at its implementation, because all dependencies are listed as parameters.
You can redefine what "pure" means if you wish, but that pretty much defeats the purpose.
The last statement isn't true unless you mean "mutability" in just the DOM elements.
All sorts of things can carry state and be mutable in a React application. Even the properties of a component are actually mutable, and it sometimes happens that people pass around arrays or objects and mutate them when they shouldn't.
Well, javascript is mutable so technically nothing is immutable. But I'm referring to working with React's hooks, where all props and state should be treated as immutable except for refs.
Refs are just as immutable in that perspective. And no, you're not supposed to mutate ref.current.anything outside useEffect or a callback or similar...
Of course you can! That's the whole point of refs, and why you can't use them as a dependency to callbacks or effects. They're totally mutable, which makes them very tricky to deal with. I mean, I wouldn't recommend updating them outside of effects/callbacks, but it's valid. For example: you can make a ref to count the number of times React calls your render loop by updating it in the body.
> And as a side note: can we please stop shitting on every React post that appears on HN? All this hater energy is really starting to be a drag.
Kind of ironic when this post kicked off a long argumentative thread shining a light on the exact things people don't like about React... "escape hatch", "impure side effects", "useEffect... huge point of failure... it sucks", arguments whether hooks and refs are immutable or not... and these comments are coming from people who claim to like React!
The purest form of function programming cannot have side effects. But to do anything useful you need them.
So like many other functional programming langs/libs/frameworks react had a controlled means to run side effects.
That’s the escape hatch of useEffect. It’s not a bad thing. It’s thematically same as the unsafe block in Rust. It allows you to reach out of the main paradigm, when required.
React was the thing that convinced me that you can't have a non-pure reactive system.
React is complex because views aren't pure; slow because it has to rerender things that didn't change, because it doesn't actually know what changed, because views are impure; hard to program because impure views create bugs that even that extra complexity can't fix; and has all those escape hatches to change execution time and memoize things because the execution time actually has semantic value.
And that's react, that incredibly well designed thing with incredibly knowledgeable authors, that made a huge effort to think about every problem.
I fully agree that impure views are a path to madness, but you don’t have to abandon React to have pure views.
React components can, and I would argue in vast majority of cases should, be pure functions. You just need to displace state management outside of React.
This removes the need for class components and hooks and returns React to its roots as a view library. Most complications in React seem to stem from its desire to also be a state library.
Case in point: we used MobX with React to build a UI for an entire document management system (essentially a file system with versioning, security and workflows) - I don’t think we have more than a handful of impure React components in the entire codebase.
Interestingly, the optimal place I see to keep state is inside the reactive system. You just have to ensure that every single non-pure action is notated as an event.
You can even have a lot of pure state interdependence. What React doesn't even try (because yes, it's a view library).
Anyway, one can certainly keep all views pure. It's a bit hard in Javascript where a lot of things are hidden, but it's perfectly doable. It's even the recommended way to use React. But that doesn't save React from being way larger, slower, and harder to use than it could otherwise be.
We've had great results from our custom built non-pure reactive system (which predates React by a couple of years).
The idea is to have an Observable object that can store arbitrary trees of data (each itself an Observable). UI drawing is generally split into a function call for each DOM element that has children. These functions are executed such that they track the use of observables and will be rerun when they change.
I've more recently, after having suffered React and friends, started creating an Open Source implementation of the pattern we used. Currently, it's mostly still lacking in documentation and marketing, and that's unlikely to change if I'm honest. But this should give you an impression if you're interested:
> because it doesn't actually know what changed, because views are impure
Because in React, the smallest unit is the component.
In Solid the smallest unit is the whatever reactive value you use, and only that changes. Components are there just to organize your code and do the initial render.
I know. A bunch of dudes talking out their ass about me calling useEffect an escape hatch when it’s literally called that IN THE DOCS. HN never disappoints.
I don't have a dog in this fight but I think that people here don't care so much about what is said, more about what is shown.
E.g. an outspokenly pro-climate politician who passed the 'frack through orphanages with baby seals bill' will not be taken seriously because actions don't match reality.
IIRC useEffect was not originally called an escape hatch and that terminology was pegged on once they realised that a fundamental hook was actually a 30mm footgun.
To make it worse they haven't provided a clear way to deal with the problem instead relying on 3rd party packages
You’re conflating clarifying intent with outright deception. The react team is trying to clarify how useEffect is intended to be used because it’s so frequently misused. The fact that it’s a possibly a poorly designed abstraction that is ripe for misuse is not really relevant to the discussion imo.
Don’t take my word for it though. Dan Abramov himself has said pretty much what I’m saying (you’d have to find it on Twitter). Ken C Dodds shared a talk (not run by him) called “Goodbye UseEffect”. Look this stuff up if you haven’t. It’s out there. There are often better ways than useEffect. And when those don’t work you can always use the esc… well you get the idea.
Calling useEffect an escape hatch is as much of a recent fad as shitting on react. How can it be an escape hatch, when it is the only api provided by react to perform side effects during the rendering cycle, which do not themselves pertain to rendering? Calling it an escape hatch is the same as calling componentDidMount or componentDidUpdate methods of the class-based api escape hatches.
I understand why it’s worded that way in this specific article. But it’s still unfortunate to call it an escape hatch.
This term is typically used for ways of breaking the rules of a framework. With useEffect you’re still very much in React world and need to follow the rules to achieve much of anything with it.
The norm would be not to use an escape hatch. But I doubt there are many React applications that aren’t using useEffect.
In useEffect your code runs at a specific point, where it’s safe to bridge react to the outside world. It’s not about breaking the rules, as much as you have the ability to cause and react to side effects (eg a fetch promise) and inform react about the result (with a setState).
I’m not against React but it sure seems like it’s more complicated than vanilla JS. The hater energy might be a clue knocking at the door, trying to quietly inform you of something you don’t want to hear.
The people who work professionally with react all day are well aware of React’s problems. But I’m a front end developer. I’m not about to change careers and become a database administrator because managing state in React is unintuitive. (And while there are other frontend frameworks and solutions, they have their own problems, and it’s more difficult to find a job using them.)
Lol "quietly". React has been around for over ten years with one of the largest grassroots ecosystems built around it. Maybe that should be a clue that it has something to offer?
I think HN is averse to it because it's blamed for "how complicated the web has become". If only we would all settle for simple static websites!
Regarding the usage of `crypto.randomUUID()` to generate unique `key` properties for list items, such a technique should only be necessary if the items don’t come with unique and stable ids on their own. This is the case for the example in the article, but in my experience, list items often do have some differentiator on their own, which you can either use directly, or otherwise derive for that purpose. If that’s the case, this is what I usually would prefer by default.
One other aspect that I find important to understand about the `key` property is that it only has to be unique among its direct siblings, but it doesn’t have to be unique globally.
Giving every item a new key on every update does not help with that - in fact it is likely strictly worse than just using index since it will cause every item to re-mount every update https://beta.reactjs.org/learn/preserving-and-resetting-stat... which is unlikely to be what you want.
Its not an optimisation, its about state management.
React can’t see the difference between a reorder and remove&insert. When reordering items the state should be moved as well; when removing and inserting a new item, state should reset.
Using an array index is equivalent to silencing the error
I was going to reply the same thing. I do have a problem with the author presenting that as a default solution instead of a workaround if the item doesn't have a unique identifier, which I have found to be uncommon.
> such a technique should only be necessary if the items don’t come with unique and stable ids on their own.
FWIW, these tips are also covered in his React course and he makes it clear in the course that this key generation technique should only be done when items don't have something better to use as the key. I think the blog post is just missing that context.
Even when not optimal, it’s good advice for the Intended audience “still pretty early in their journey”
> crypto.randomUUID is a method built into the browser (it's not a third-party package). It's available in all major browsers. It has nothing to do with cryptocurrencies
The really subtle footgun that I see is unexpected state preservation. This is where the component isn't designed to be "reused" for completely different props. This is because the state has something about the props and will end up unhappy if they change.
Some common warning signs are generic parameters for components and using a prop as the default value for a state hook. But neither of these are a reliable indicator of a problem.
These bugs also often lie hidden for a long time because They can be hard to trigger because you need the same structure of components with a generic property changed. But when they bite they seem like impossible errors.
I've wondered if React could add a useStateKey([deps]) hook that would allow forcing a state reset if the given values change. This would work similar to a key attribute but be controllable by the code inside the component.
This was more explicit when implementing class based components than functional components because the class based API had explicit overridable methods for those aspects of the component lifecycle.
That issue probably wouldn’t exist except that React has to make some concessions to performance, therefore component reuse, therefore component state working in sometimes unintuitive ways.
This follows on from using props as an initializer - something that should be avoided if possible. Date pickers get gnarly here: is the date prop the initial date or the current one? I think it should be the current one, with a prop to handle date change.
Most (7 out of 9) of these don't happen when using React from ClojureScript via the most common way, Reagent:
"Evaluating with zero" doesn't apply because you use Reagent's builtin Hiccup functionality instead of JSX and you have no temptation to use anything like these single-js-expression code patterns because, well, there's no statement/expression divide in Clojure.
"Mutating state" doesn't happen because you don't mutate things anyway.
"Not generating keys" does happen.
"Missing whitespace" does happen (I think?)
"Accessing state after changing it" - Nobody uses state hooks as it's just worse than the normal atoms way, although there's a way to do it.
"Returning multiple elements" doesn't happen since, well, you can return multiple elements, components return vectors (Hiccup is vectors based data structure).
"Flipping from uncontrolled to controlled" again is state hook specific, which are practically unused.
"Missing style brackets" is JSX specific, doesn't happen in Hiccup.
"Async effect function" doesn't happen.
(The story is mostly the same with Reagent alternatives too)
My personal experience is that using reagent is so intuitive that I had launched a non trivial form to post ads on a production site within a week not knowing both clojure/script and anything about react.
For the examples regarding Mutating state and Accessing state after changing it I believe it would've been a great chance to teach about "functional updates" on the `set` functions, which can be accomplished by passing a function as an argument instead of the `nextState` value. (ref https://beta.reactjs.org/reference/react/useState#setstate)
I just finished reading and I've run into every single problem listed here at some point in my career. These are all really solid tips, and it's also kind of validating to see that some of the things that tripped me up in the past are apparently pretty common tripping points.
It is also quite useful to check https://ahooks.js.org/hooks/ directly: Observing that there are hooks for seemingly basic tasks, and reading their linked code, makes it easier to figure out more React anti-patterns, simply by seeing what the hooks author did instead.
I really wish the official documentation was better. Often the docs are self-referential, referring to how current approaches are better than thing X React did in the past, and then the trail gets lost, instead of explaining from first principles how things work and why they are done the current way.
Unless I'm missing something, I think the "Async effect function" solution is not correct:
React.useEffect(() => {
async function runEffect() {
// Effect logic here
}
runEffect();
return () => {
// Cleanup logic here
}
}, [userId]);
This looks like a race-condition where cleanup may be done before `runEffect` has finishes resolving, and any errors thrown in `runEffect` will be unhandled promise rejections.
This should be number 1 on the list, and it's wrong.
Specifically:
React.useEffect(() => {
// Create an async function...
async function runEffect() {
const url = `${API}/get-profile?id=${userId}`;
const res = await fetch(url);
const json = await res.json(); <-- OK
setUser(json); <--- Wrong.
}
```
This is, hands down, the #1 mistake that react developers make. I've seen it literally in every react code base I've ever opened (if the code base had any kind of `fetch` in it).
It's covered in the documentation now, explicitly:
This is a prime example of a sharp edge with react hooks; it's a very very common pattern, but because people don't understand what's going on, they do it wrong naively; and even when you do know it's wrong, it's a rare edge-case that causes an invisible bug ("Tried to update a component that wasn't mounted...") and makes code messy and hard to read.
The tldr should be: Do not use async effects. Use suspense.
The section in the beta docs you linked to shows an async function startFetching() which does setTodos(json). How does that reconcile with you saying that setUser(json) is wrong?
The first thing I noticed looking at React after Svelte was that Svelte had syntax for promises from v1 while dealing with Promises in "the most popular framework on the plane" is still a PITA.
> Missing whitespace. I've since realized that there is no perfect solution to this problem.
Indeed :)
HTML had the same problem, albeit with different and equally frustrating choices.
The problem is inherent to markup languages: difficulty separating intentional content from internal formatting. That is the both the feature and bug of markup.
I love React as long as it has a thin skim of clojurescript over top. Rum is the underdog compared to reagent but is still my weapon of choice - https://github.com/tonsky/rum
Was disillusioned when I had to dive into a pure js project using React.
The real benefit, I think, is that you get the well established Clojure idioms around isolating and managing mutable state.
State is stored in a Atom, which is atomically mutated, and reactive components essentially 'subscribe' to updates upon that atom to re render.
The mutations can be handled centrally by a message queue, but really, event sourcing like that is not always needed.
Depends on the perspective. I know a bit of Clojure, but for me those idioms are hardly "well established". For me, the patterns of React, Typescript and Redux are well established...
I believe these functional React and Redux patterns got largely inspired by the Clojure ecosystem. It took a while for the adoption of functional patterns in the JS world.
But I believe what OP meant is really more about how these idioms are holistically implemented throughout Clojure. Whereas in JS we have to fight the language quite often.
It became so steadily imprinted into minds of developers that very few realize how nuts it all is. Back in 2000 nobody in their mind would take seriously these perverted constructs loaded with footguns to create a form. And even if you are aware of what’s going on, this thing itself struggles to solve trivial tasks in non-twisted ways. Sigh.
I'm a fairly seasoned react developer and I've been thinking about this debate that have been going on lately. Meanwhile I'm writing a fairly involved front end app in vanilla JavaScript and a bit of jQuery. It's a blast from the past for me.
I went into this period of debate with what I feel is an open mind. Open to the possibility that react really is overrated and more trouble than it's worth.
But after writing this app I'm very well reminded of why I was so enthusiastic about react way back when. Handling state updates in vanilla JavaScript is quite messy.
Then again, react is far from a perfect solution to the problem. It adds about as much complications as it takes away.
I hope we can find better solutions. But I'm not sure how to move forward when it seems so hard to agree on what the problem is.
As I see it, the problem is that: (1) react tries to teach everyone new bizarre ways of writing code instead of being a toolkit, (2) web is historically so focused on old-crazy-web <-> new-crazy-web dichotomy that cannot see past it.
(1a) is the web’s desire to mix “ui rendering” with “state management” which are two different things.
State management has lead to a bunch of different frameworks (e.g. redux, mobx, zustand). They are also not really pleasent to use and sort of mix into UI rendering.
How would your approach at seperating state from UI look like?
Creating all but the most basic of forms with plain html/css/js is loaded with footguns too. Add in conditional fields, multi-page flows, loading and error states, etc then it becomes clear why solutions like React were created and why they have become so popular.
It is very true that the types of situations you mentioned are very painful with just HTML/CSS/Vanilla JS. However, React is no longer the only game in town for the things you mentioned. LowJS frameworks like Unpoly with a good server side rendering framework can handle the things you've described and a lot more. For example, you can use Unpoly with a server-side framework to allow layers to either act independently or to cascade information down to lower layers for multi-page/modal window flows that need to share information all without full page reloads. [1] Using layers you could build some pretty decent sized apps, Unpoly gives an example [1] showing (conceptually) how you could build large parts of Gmail with layers. You can also do things like dynamic forms with a bit of code on the back end. You get all this without needing to keep state in sync between the front end and the back end.
There will be cases where Unpoly (which can do quite a lot) only does 95% (or whatever) of your requirements. In those cases you can sprinkle in a bit of Vanilla JS to help fill the gap. Additionally, there will be some cases where Unpoly isn't at all suitable, like if you're building something that is extremely interactive (i.e. the next Google Docs/Maps or similar). However, those cases keep getting smaller as these LowJS Frameworks improve. Everyone's case is a very particular set of circumstances and this might not apply at all to your situation, but others are finding success with using HTML over the wire types of approaches. [3] One final thing to note that's often overlooked is that CSS' capabilities are growing, and can do a more than in the past. All things to think about before reflexively defaulting to React.
> In those cases you can sprinkle in a bit of Vanilla JS to help fill the gap.
I think this is where your proposition causes me to raise an eyebrow.
If you can stay 100% within your framework then I can accept it will be simpler and more productive than React.
But this architecture of “95% in-framework and 5% JavaScript spaghetti”… I don’t trust that. That sounds like a lot of days burnt trying to bolt that one feature onto a framework that doesn’t support them. And a lot of days debugging into the framework internals to try to understand which possible implementation of that feature won’t blow up by crossing some assumption of the framework. And I’m not sure you can argue as convincingly that’s less effort than the 100% React solution.
IMO the beauty of React is you can do anything you’re asked to do. There’s alway a “React-y” way to do something. That’s because it’s lower level than a framework. It’s just a set of primitives. And pretty well thought out, battle tested primitives at that.
And, when you do it right, you can have a very large piece of software that’s made entirely of small, functional modules. And there are simple rules for following the control flow. Once you understand those rules (and it does take a few years to really grok them) then you can drop in to any module in a massive system and follow the control flow easily.
In a framework you don’t have that. There’s various points where your code calls into some declarative interface the framework has exposed, and then *magic happens* and the wrong behavior pops out on some other end of the application. It’s a debugging brick wall and in my experience it’s a massive time sink. Even in React many third party libraries also put you in this position, but that’s a whole other rant…
But that is React’s superpower: when you use it correctly your time spent debugging any one defect is closer to O(1). With more “black box” frameworks your debugging time can be more like O(n), where n is the size of the codebase.
For small codebases, there’s no difference, or maybe the framework is faster even. But for large codebases I think React has a massive productivity boost there.
And all of this is predicated on that 5% number you quoted. If you can stay 100% within the framework, go for it. But in my experience business needs are always going be pushing 5%, 10%, 20% of the “special requests” and if you don’t plan for that, they can start taking up 50%, 60%, 70% of your time.
There are ways to most anything. The question is whether it is a good or not. The best/worst thing React did to web development is taking a cleaver and splitting the front end and the back end down the middle. It is the best because it provided a clear answer to adding dynamic content that didn't end up a horrible mess like jQuery at scale. However, the worst thing it did was split the front end and the back end. The typical setup of, a front and developer and a backend developer, often on separate teams is a big problem spawned by React. Whenever you want to do anything new in these situations, you need to meet with the backend developers to build endpoints for you. This slows things down tremendously and creates friction during development. Even moderately interactive pages, going through all of this is way overkill and you're very likely wasting so much time (all other things being equal). With traditional server-side frameworks (e.g., Laravel, Django, Rails, etc.) you don't need to wait for anyone to build your endpoints or set up a byzantine labyrinth of JS tooling before you can code the first line. Don't get me wrong, React is a great tool and the best tool to building many SPAs, but it isn't the right tool for every job. There's a reason why so many people in this post are saying they've had it with React, which isn't something one used to see on this site. Now it's fairly commonplace to see people looking for greener pastures.
> But this architecture of “95% in-framework and 5% JavaScript spaghetti”… I don’t trust that. That sounds like a lot of days burnt trying to bolt that one feature onto a framework that doesn’t support them. And a lot of days debugging into the framework internals to try to understand which possible implementation of that feature won’t blow up by crossing some assumption of the framework. And I’m not sure you can argue as convincingly that’s less effort than the 100% React solution.
That's a pretty easy answer, you can drop in a React/VanillaJS Web Components or VueJS Components whenever you like. If you're still feeling unsure, just go with Hotwire, which has an small JS framework (Stimulus) for dealing with the odd bits of Vanilla JS to give your HTML a bit more interactivity. Now you have a belts and suspenders backstop to handle feature creep and increasing interactivity. There are other tools for doing similar things to Stimulus.
> But that is React’s superpower: when you use it correctly your time spent debugging any one defect is closer to O(1).
Nothing is for free. The tradeoff is that you're going to spend more time figuring out state sync issues/debugging/refactoring your API endpoints.
> And all of this is predicated on that 5% number you quoted. If you can stay 100% within the framework, go for it. But in my experience business needs are always going be pushing 5%, 10%, 20% of the “special requests” and if you don’t plan for that, they can start taking up 50%, 60%, 70% of your time.
In an effort gently persuade, I may have undersold just how little (i.e., 0%) Javascript you'll need to write for many types of common pages and patterns.
HTML over the wire frameworks all came out around 2015-2016, which means they're all battle tested and have seen a lot of use cases. Turbolinks/Hotwire and Unpoly both came out of companies who are presently or started out as web agencies. Basecamp has been powered by HTML over the wire for years. Now Hey.com email is showing what this approach can do for a pretty ambitious application. Part of the founder's pitch for Unpoly is that he uses Unpoly and in his agency and supports his apps for a long time, so you need not worry about it going anywhere. Because of this, the number of use cases these frameworks have seen and handle is significant. Web dev shops constantly deal with exactly the sort of feature creep you mentioned. If these frameworks were a big problem or had huge limitations that you're concerned about, their founders supporting companies would have abandoned them long ago, to say nothing about all their users. Time is money in web agencies, so I would be surprised if they experience the situation you're describing.
I'm not saying HTML over the wire is the end-all-be-all, only that they are good enough today to remove the always default to React mindset for many shops and situations.
I could not agree more. At my current gig we have forms spanning 6 pages, where the state of each sub-form needs to be preserved, pretty much half of the form fields are conditionally loaded across all pages. There is tons of dynamic data displayed and sometimes that needs to be transformed client side, has loading and error states and there is heaps of validation happening, some of it asynchronously and it involves calling other services. Some of the inputs should be also preserved for other parts of the page regardless of whether the user fully completes all forms or not and then there is a whole basket of custom tracking that the marketing team insists needs to happen.
You could of course argue that this is bad UX or that the problem starts with the requirements but that’s usually not something engineers a lot of have control over so I am actually quiet grateful that we have a framework that lets me solve this (imho) more sanely than if I were to hack away in vanilla JS.
I wonder you have ever tried doing all of this in vanilla js. Of course you will end up with your own tiny lib, but it's definitely not as hard as React.
Nobody's telling you to use React when you have a static HTML page. But if you ever worked on an app of even medium complexity, you would understand what React is solving for developers. State management is the problem, and your vanilla JS forms are not going to cut it.
The problem is, I worked on these, half of my life. And while I agree that declarative rendering has its upsides, I am not blind to downsides and bait-and-switch-complexity tricks. React is pretending to be a functional “not your concern how it’s done” language without any safety nets or execution models these usually provide.
If we agree that declarative rendering has its upsides, then it is ridiculous to keep moaning that the complexity is "nuts" and that everyone's just insane for no reason. React is what a declarative model looks like. It eliminates an entire class of UI, state, and behavior management issues, in exchange for working within its rendering engine. If you're looking to build a complex frontend, that is extremely attractive.
> If we agree that declarative rendering has its upsides, then it is ridiculous to keep moaning that the complexity is "nuts" and that everyone's just insane for no reason. React is what a declarative model looks like.
Are you saying that Vue isn't a declarative model?
No, of course. Back in 2000 we loved handling our own window.__mynamespace variables. The world was so much better with document.forms[0].submit. Those beautiful days of document.write, document.elementid.property. I’m nostalgic.
Web was always less disciplined in this regard, compared to desktop. Being a desktop app developer back in 2000 you’d just pack some widgets together, set handlers for single-value widgets to fire into a controller object, set up data sources for tables and that’s it. No markup concatenation or implicit on-return behavior.
There were thousands of pre-react desktop apps on frameworks from turbovision to *tk to appkit and not a single one of them mismanaged focus because of a missing “key” or required to “update state through slicing an array” nonsense. This old pain you feel is historically self-inflicted and doesn’t exist elsewhere.
I made a bunch of apps and user interfaces with these frameworks myself. It was natural and straightforward. Sometimes it could get clunky and hack-y due to limitations, but usually it was the level when a whole react team would be fired for economical reasons (think of multi-tab forms with settings, dialogs, master-details, complex table input, etc).
But it all sucked big time because what we all wanted, was to run those apps in the browser and on the desktop. Adobe Flex was the first real thing that came close: you could have loaded a swf file into an air wrapper. But Flash is dead, Flex is dead. And now we have what we have. It could have been worse.
Frankly, Flex did s lot of things right. The layout and the default widget library was top notch. The grid was a killer feature. The IDE was really helpful.
Most of these mistakes are programming mistakes. You’d be in trouble doing the && shortcut in a lot of places.
And second, not at all. E4X existed in the 2000s and was widely implemented in Firefox (then the biggest modern browser) and Flash (the other de facto runtime). E4X let you embed XML directly in JS and you basically were able to build React-style templating without the preprocesor that React requires.
So basically the world has been working on something like React for 2 decades. React just is so far the best version of SGML in code.
How would you advise someone learn frontend development with React? The React docs are phenomenal, but they're just docs. I'm looking for something more structured, but most of the books I've found seem kinda cobbled together and low quality
React was a great idea; the problem was they did it in JavaScript instead of a pure, functional language. There's too many footguns where JS features like mutability and async code can leak through, leading to a whole class of errors that could have been caught by a compiler but instead were left as cognitive load imposed on developers.
If I'm not mistaken, the issue with trying to read your writes is more about the fact that JS has no opportunity to update the variable until you run the function again:
It's not about JS, it's because React won't let you render an inconsistent view of state. `setState` allows it to queue up multiple state changes, commit them all, and only then re-render your component. So you should think of `setState` as "queuing up a change to be applied for the next render cycle".
Thanks for the clarification. I had thought that React updates state immediately and may preform multiple re-renders prior to committing to the DOM, and it's the re-renders which are queued.
React _usually_ queues up a render, which is then executed at the end of the event loop tick. This allows many calls to `setState()` in the same tick to result in a single render pass at the end.
The default behavior is that _if_ there are any changes that are needed based on the render, React then continues ahead and applies them to the DOM, and most of the time that is done immediately.
When a render is queued, React keeps the info about the requested state update as part of the queue, and those state changes are calculated and applied as part of the "render phase". So, if you happen to call `setState(2); setState(3);`, the second call completely overrides the first and there won't even be an attempt to render the component using the `2` value. (You can use the `setState(prevValue => 3)` form to force React to do each value separately.)
As of React 18, the new "concurrent rendering" features like `startTransition` and Suspense allow React to alter priorities of renders, and split the calculation into multiple chunks over time with pauses in between. When React _is_ done determining any changes in that render pass, it still applies them to the DOM in a single synchronous "commit phase".
The other caveat is that React will sometimes execute a full render+commit synchronously under specific conditions, such as a `setState` inside of a `useLayoutEffect` hook.
I wrote an extensive detailed post called "A (Mostly) Complete Guide to React Rendering Behavior" that tries to explain a lot of these nuances:
Both are valid, but you’re right in that, at least in my experience, the second option can be safer. Specifically when updating an object or list via an asynchronous call where the calls can change based on state (such as the user changing accounts)
In my experience, that form of useState is an anti-pattern…
It’s not that it doesn’t work, it does.
But I’ve found there are basically two scenarios:
1) Simple cases where there’s no risk of contention and you can just operate as if your state variable is up-to-date.
2) Complex cases where you have state changes coming from multiple directions and contention is a concern.
In the second scenario I just go straight to useReducer. It is much more reliable than useState when there is the possibility of contention. And it’s much easier to reason about than having a bunch of setState calls all over.
IMO the setState((oldState) => newState) pattern is a half measure that’s almost never the best choice.
For me the most important thing in a React app is to maintain readability, and I think the functional control flow helps with that. My initial reaction to signals is that they’ll hurt that readability.
But I haven’t used them personally so I don’t know. Will be interesting to see how folks do with them.
It may be trite to even mention it at this point, but for fun I gave all the example code in the article to ChatGPT and it correctly explained the problem in all but one case (the whitespace one). Pretty cool!
Correct me if I am wrong but wouldn’t the best solution to the “Accessing state after changing it” problem be to pass a callback as the 2nd arg to the “setState” function?
I'll be a Svelte evangelist till the day I die, I avoided the whole early reactivity phase because of how off-putting React was, finally started when Vue became slightly stable, and completely switched to Svelte when ractive.js was being sunsetted and Sapper/Svelte was the new thing in town. Haven't looked back since. Even now I really think half the reason everybody jumps on the React bandwagon is because everybody is using it for some inconcievable reason. Heck they won't even look at Solid or Svelte. React state management honestly looks like it was tailor made to torture you. Reminds me of how painful RecyclerView used to be before Compose.
well, great timing, "Ask HN: What's the fastest and simplest way to prototype a web app in 2023?" [0] is on the front page. It seems like every year the list changes. Seems like everyone uses react but nobody recommends it. Such a bizarre ecosystem.
It is just a joke. React is most popular, so it is the most risk averse choice. Vue is better in many technical areas, but there is not that popular. So that is a trade off that you need to consider. And many business would prefer to be risk averse
These "mistakes" are presented as React specific but I think most of them are general programming mistakes. Or perhaps JavaScript mistakes if your please, since not all languages allow you to make all of the mistakes with mutating and using state.
For example the "Accessing state after changing it" has absolutely nothing to do with React or async code, and everything to do with the scope of the count variable and how JavaScript passes numbers by value.
Without React (or a similar framework) JavaScript makes the assignment immediately. It’s not like the language has some special handling compared to other languages in this regard.
React, otoh, provides methods that access and update state. React’s handling of changing state using these methods is to queue the change, not to update it immediately.
function handleClick() {
setCount(count + 1);
console.log({ count });
}
Since count is enclosed within the function, it could be mutated by _anything_. In isolation, you can't really be sure of what setCount does, since the "count + 1" statement passes a completely new value unrelated to count.
If setCount didn't have any side effects, it is an obvious mistake to assume the value of "count" would have changed.
So if the intention is to log the incremented count, it is bad code in any situation, React or not.
Without further context, that looks like a property setter. There’s absolutely nothing wrong with thinking that the property setter sets the property immediately.
What you’re saying amounts to “functions/methods can do whatever they want, and assuming they do what they claim to do is an obvious mistake.”
One of the biggest issues in React that I have come across is that you have to make copies of objects and arrays (as mentioned in this article) when calling setState(). Cloning works well for small arrays and objects (I use the code below for that), but for large arrays with thousands of items (for example, datagrid scenario) this is inefficient. My solution is to modify the array/object directly then just call setState({}). Does anyone have a better solution?
export function deepClone<T>(obj: T): T {
if (obj == null || typeof obj !== 'object')
return obj;
const clone = new (obj as any).constructor();
for (let key in obj) {
if (obj.hasOwnProperty(key))
clone[key] = deepClone(obj[key]);
}
return clone;
}
Deep cloning is almost never the right answer - you'll end up copying _too many_ object references, which is extra work, and that can also lead to unnecessary re-renders depending on what your components are trying to do.
A correct immutable update is more like a "nested shallow update", copying all levels of nested keypaths leading to the actual value you want to update.
If you prefer to avoid writing those immutable operations by hand, Immer is a fantastic tool for simplifying the code by using "mutating" syntax that gets turned into a safe and correct immutable update:
immer makes an "efficient copy" of the object, right? Keeping references to some nested objects in the original object when possible? If so that's cool... but not sure it is worth the effort.
Immer does a correct nested immutable update, same as if you wrote the corresponding nested spread operations by hand. It _only_ updates the nesting paths that led to the value that got modified.
So yes, if you have a nested object with a bunch of different fields, and you update `state.a.b.c = 123`, it makes copies of `b`, `a`, and `state`, but preserves all other nested references that are anywhere inside of `state`.
Others have mentioned efficient ways to work with immutable data structures, but I want to mention another solution:
In cases where I want to get ideal performance, I sometimes separate out a plain JavaScript service and wrap it in a React hook.
So for example, let’s say you have a massive array of like 100,000 objects. You presumably aren’t putting that entire array on the screen at once. So you could keep the array in a normal JavaScript class, keep in instance of that class in state, fire events into it, and then query out the slices of data you actually need.
Those smaller slices can be generated each time there is a state change, but you don’t need to re-allocate the big array.
Even if you do need to show the entire array, say as a data plot, you can render directly to a canvas, but still wrap that canvas and the I/O in a React hook. React works quite well with these “plain JavaScript” escape hatches in the rare case they’re needed.
The other place I’ve been using this approach is for drag and drop hooks, where the complexity of managing React renders and coordinating state changes on every mouse move is just too much. Instead I have service class that updates the few DOM nodes that need to be changed every frame, and I only fire off state changes when there are meaningful transitions (hover over something, drop something, etc.)
This is what the "immutability-helper" library and immer are used for. Or Immutablejs to go even more fancy.
It's not that expensive. You don't need to deep clone an array to change one element, you just create a new array linking to all the old elements, except the one you want to change plus the new version. If you understand "referential equality", this will make a lot more sense.
You only need to update the objects that actually changed, and only along the path of the keys that changed. Deep cloning is overkill because you'll incur way too many updates.
Check out useReducer and all your problems go away! (It has no relation to Redux, many people think this but it’s a native React hook that makes updating keys within an object much easier)
I can crank out tools in minutes. No complicated build systems or web pack or dependency management system. No react, no reduce, no apollo or graphql. No typescript, etc.
Just simple go, html, css, and a bit of javascript when it's needed for a form. I don't minimize anything, or try to do anything fancy. It faster to develop in, and faster to load in the browser.
I'm specifically talking about internal tools here.