Common Beginner Mistakes with React
joshwcomeau.com
joshwcomeau.com
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.
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.
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.
Until you get told to implement more reactive features, then it's over. Using Pure JS/JQuery for reactiveness is outright horrible.
You have to do validation on the server anyway. Why do it again on the front end?
(:
Sorry, those turn up as 'must have' features in like, meeting #2 with the product owner, because the UI feels old and rubbish without them.
Technically? You're 100% right.
...but, I guarantee that even a LOB app, this will turn up, again and again and again as a feature request, until it becomes a tier 1 priority.
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.
For simple projects, the validation built in to HTML form elements may be sufficient.
Yes you do.
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.
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.
The thing is maintaining simple state given the current abilities of css and JavaScript is a little bit more verbose in React than otherwise.
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.
Turbolinks/hotwire? Not as easy with Go as with Ruby but can be done
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.
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.
Vue 3 has excellent Typescript support. It's even built in Typescript. What exactly are you referring to when you say that it's "poor"?
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.
If you have a basic project the tools should be basic.
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.
There are times where React/Vue/SPAs are warranted. It's not 100% of the time, however.
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.
For example, this file https://github.com/sk0g/peripheral-emulator-web-app/blob/mas...
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.
fewer conditions now than it used to be, but you still need to do this at times.
Usually I take it as a sign I should move to using a store.
Sveltekit is very good.
React generates billions of dollars every day.
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.
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.
So you basically stopped being a fullstack dev, and moved to the back-end.
Sit down and carefully consider what is the difference between front-end and back-end development, and why that distinction exists in the first place.
PHP and RoR was frontend.
With the rise of SPAs I've seen "traditional frontend" rebranded as "backend of the frontend" which is an awkward term to say the least.
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.
> 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.
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?
Case closed. Have a nice day.
[1] https://en.wikipedia.org/wiki/Front-end_web_development
[2] https://www.ycombinator.com/jobs
[3] https://www.ycombinator.com/companies/golinks/jobs/k3k6PSz-f...
[4] https://www.ycombinator.com/companies/tractian/jobs/P6ri7Wt-...
[5] https://www.ycombinator.com/companies/svix/jobs/7DMKXxB-rust...
[6] https://www.ycombinator.com/companies/safebeat/jobs/2mJ95eL-...
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"
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?
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.
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.
Source: So have I.
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.
They did not call themselves "full-stack" though, did they?
Sep 2010, a few job ads are looking for "full-stack developers": https://news.ycombinator.com/item?id=1659409
Comment from @peteforde on Dec 2010 https://news.ycombinator.com/item?id=2053957
====
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...
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.
That’s great for you if your home is close enough, then of course you should have been walking. But it’s not exactly generally applicable advice.
The whole point of React is to create interactive UIs, that’s why it’s called “react”. And you can’t do that with a templating system.
I love statements like this. It's as if things are just complicated for the fun of it, not because they need to be/they solve complex problems.
Flow is much better in this area.
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.
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".
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.
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.
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)
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 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.
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.
And don’t be afraid to use this approach outside internal tools.
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.
Some things need fancy react GUIs. Others need to surface business data to a small number of people in a simple and cost-effective way.
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.
The truth is solution is in the middle. Hotwire/Liveview type.
This way you can have the performance and simplicity of Go with the reactivity of SPA and the SEO should you need it, I know, internal tools.
The only package to my knowledge in Go doing this is "Kyoto" but it's far far away from being proper or in a easily usable state.
If anyone knows any other Go packages that do hot/liveview please, I'm all ears.
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.
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.
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.
But there is a lot of mutable code that could be immutable, and `useEffect` is how that happens in React.
> 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The react docs literally call it an escape hatch [0]. First sentence.
[0]https://beta.reactjs.org/learn/you-might-not-need-an-effect
useEffect let’s you safely communicate with the world outside react (ie cause side effects). It’s the only reliable way to …escape.
The original docs made no such claim.
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.
The original docs are outdated
The abstraction itself is ripe for misuse
Tons of devs use it when they shouldn’t.
Insert shrug emoji here.
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.
The beta react docs have a number of great examples of this!
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!
[0]https://beta.reactjs.org/learn/you-might-not-need-an-effect
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.
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!
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
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.
EDIT: spelling
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 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.
Non-pure reactive systems just do not work.
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:
https://github.com/vanviegen/aberdeen/blob/master/examples/t...
Curious to know your thoughts about this being an effective non-pure reactive system.
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.
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.
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.
cf. "can be please stop hyping .. [favoured tech of the month/year/decade]"
Talk about pissing into the wind.
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.
fwiw the author specifies says not to do this. The advice is to generate a unique ID when items are created, not when they’re rendered.
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
It depends on what your data source is. If it’s a stable sorted list that only ever gets appended at the end, you can use the index.
At the end of the day, there is no hard fast rule for the key. You have to know what your data source is.
You have to know what you’re doing.
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.
> 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
Funny, but sorry state of affairs: we need a new word for cryptography [1]:: mysticography [2]? "About 1 results" in Google Search, that's a first.
[1] https://www.etymonline.com/search?q=cryptography
[2] https://en.wiktionary.org/wiki/%CE%BC%CF%85%CF%83%CF%84%CE%B...
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.
I agree
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.
"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)
For others' context, it's something like this:
setFoo((f) => f + 1);Another post I found insightful is this one, translated from a post by the author of the Alibaba Hooks library:
https://enlear.academy/5-things-i-disagree-with-react-hooks-...
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.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:
https://beta.reactjs.org/learn/synchronizing-with-effects#fe...
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.
I have to wonder how much pain would have been saved if the React team had just shipped a `usePromise` hook years ago.
"await blocks" on http://web.archive.org/web/20180103230006/http://svelte.tech...
> Do not use async effects. Use suspense.
The linked documentation does not mention the word "suspense".
Do you have a link that combines these two topics?
Instead the React docs explain a workaround that leads to completely different network request behaviour across development and production:
> In development, you will see two fetches in the Network tab. > In production, there will only be one request.
The proposed solution in the docs now:
> useSomeDataLibrary()
without saying what that "some data library" could be.
Curiously, it's also very similar to the example in the beta docs that wokwokwok just linked to.
> 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.
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.
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.
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.
[1] https://unpoly.com/up.layer
[2] https://unpoly.com/tutorial
[3] https://htmx.org/essays/a-real-world-react-to-htmx-port/
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.
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.
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.
(1a) is the web’s desire to mix “ui rendering” with “state management” which are two different things.
How would your approach at seperating state from UI look like?
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).
Edit: “slicing”
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.
Are you saying that Vue isn't a declarative model?
full syllabus here: https://fullstackopen.com/en/#course-contents
const [state, setState] = useHook("a")
...
setState("b")
console.log(state)
I could imagine React adopting Signals and ending up with something like: const [accessor, setState] = useSignal("a")
...
setState("b")
console.log(accessor())I wonder how Preact works with Signals, then?
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:
- https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-...
function handleAddItem(value) {
const nextItems = [...items, value];
setItems(nextItems);
}
should be? function handleAddItem(value) {
setItems(items => [...items, value]);
}It might come back and bite you if you implement something that performs an async update of that state via a different path later.
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.
Any thought on signals? They seem to be hyped as the next big thing for managing state, haven't used them myself yet though.
But I haven’t used them personally so I don’t know. Will be interesting to see how folks do with them.
So by me not so much trite as faux pas.
:)
Cue the downvotes....
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.
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.
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.”
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;
}PS Why did you post code a `deepClone` function? You certainly don't need to deep clone.
For example:
const [largeArray, setLargeArray] = useState<MyObj[]>([]);
const updateObject = (index: number) => (newObj: MyObj) => {
setLargeArray(prevArray => ([
...prevArray.slice(0, index),
newObj,
...prevArray.slice(index+1),
]));
}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:
- https://immerjs.github.io/immer/
- https://beta.reactjs.org/learn/updating-objects-in-state#wri...
We even use Immer by default in Redux Toolkit:
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`.
It's the same as if you wrote:
return {
...state,
a: {
...state.a,
b: {
...state.a.b,
c: 123
}
}
}
but obviously much shorter and easier to read / maintain :)It also conveniently eliminates the chance of a _real_ accidental mutation (which in the case of Redux was always the #1 cause of bugs in Redux apps).
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.
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.)