1. The author complains that React devs don't understand addEventListener when it is used and required reasonably often as far as I have seen?
Also, "Again: the web had this. The browser’s event system is fast, flexible, and well-understood. But it wasn’t controlled enough for the FP ideal of a closed system."
The stated purpose of the synthetic event system was to normalize differences in browser behavior back when React was created. It's not as necessary these days because browsers have gotten better, but saying it's about "FP Idealism" seems off.
2. The author complains about re-inventing the <dialog> element. This is valid, and new code should obviously use it. That said, it only became available across all major browsers in March of 2022, and still isn't supported by some minor browsers.
All of the popular component libraries were made before that, and thus ought to keep their JS implementation for backwards compat.
As far as tutorials, when I search "React dialog tutorial" the first result regrettably does show how to build your own, but the next two focus on using the built in element.
3. Then the author turns to customized select, and complains that JS frameworks don't support them. This would be valid but the very MDN article they link literally starts with:
> This article explains how to create fully-customized <select> elements using *experimental browser features*
(emphasis mine)
`<dialog>` only got full compatibility with Firefox about two months ago with Firefox 141. Safari still doesn't support `closedby`.
Given that, I still regard `<dialog>` as experimental, and that'll remain the case as long as the "Baseline Widely available" label comes with an asterisk.
And the only reason it suddenly reappeared in 2022 was because browsers want to get rid of alert/prompt dialogs.
The spec was (and probably still is) so riddled with issues that it never went anywhere for 10 or so years, and Chrome at one point argued it should be removed.
> This would be valid but the very MDN article they link literally starts with
People involved with HTMX play really fast and lose with anything that doesn't fit their conclusions hoping no one will call them out.
Do we?
https://htmx.org/essays/when-to-use-hypermedia/
https://htmx.org/essays/#on-the-other-hand
https://htmx.org/essays/alternatives/
Do other libraries talk as much about when they are and when they aren't appropriate to use? Do they link to alternatives? Do they link to bad experiences with their own software?
Valid questions we should all be asking. Sometimes, just because “that’s the way it was” doesn’t mean that’s the way it should be. Often times I find myself deleting code that is now more readily available as either part of the platform I’m on or part of the framework I’m using.
Your points are valid too, that they don’t know about the history that got us here and why it is the way it is but nevertheless we should all be asking ourselves daily, “do we still need this?” and trim the fat.
I felt cheated just by having read the article: I'm all for a long and winding article that has something to say, but this one was telling a bad joke for WAY, WAY too many paragraphs.
React is the farthest thing from functional, see here: https://mckoder.medium.com/why-react-is-not-functional-b1ed1...
Composition in React Hooks is closer to multiple inheritance in C++ (because it combines state and behavior from multiple components) than functional programming.
In my mind, SCSS + CSS modules + maybe a processor tool is an rock solid and modern set of tech that produces excellent results and most importantly moves styling off the main thread. It makes sense to use it, but we don't. FE interviews even for senior+ roles are JS/React/system design questions. Nothing about CSS and I get it. Why interview for something you don't use internally?
I recently read something that stuck with me, which was about micro front ends but I think applies in more cases than this: "it doesn't solve a technical problem but an organizational one".
There was an excellent reddit discussion on the pros and cons of tailwind and it boiled down to "it's really hard to enforce CSS guidelines for teams of multiple people". Tech leads didn't want to monitor how 10 or 20 or 50+ different FE developers wrote CSS and opted for tailwind so that everyone wrote the same, even if that meant multiple inline classes pasted on each element. I find this reluctance to enforcing guidelines weird, considering at $WORK we have multiple confluence pages and internal documents about React and Javascript guidelines and I have seen similar documents in previous work places. Would it be really different to apply the same mental paradigm for CSS?
Of course, all this is under the hindsight knowledge that HTML and CSS have evolved in recent years to be truly powerful and versatile. I get the technical decision to go all in on JSS and React 5 years ago. I don't now.
In my view CSS is essential. Not knowing CSS at least somewhat well is a huge obstacle in producing high quality frontend work. It's like being a carpenter, but simply not knowing one important aspect of wood, or not being able to use a specific tool to work with wood, lets say a tool to smooth surfaces. CSS is part of the medium you work with as a FE engineer. It is unfathomable to me, how a FE engineer can not know this stuff well. If some FE engineer is reading this, and feels some impostor syndrome: Yes, if you don't know your medium and tools as least in the basics, then you should feel like an impostor.
I see broken responsiveness very often. Of course in almost all websites, that rely on JS to display what is essentially a bunch of static texts.
If I was interviewing for a FE position, and really had to go through the circus of asking interviewees code questions, I would definitely include a minimum of CSS knowledge there. Basic things like how they would scope their CSS to specific elements or classes of elements and how they would prevent their styling to bleed into other stuff. Or how they would set up a theme with just CSS. Not questions expecting them to write CSS on a whiteboard, of course. Just testing their basic understanding.
I have interviewed FE devs and from years they are absolutely unable to implement a native form.
"my set of non-standard tools and preprocessors is superior to these guys' non-standard tools and preprocessors" is not a good argument.
> I find this reluctance to enforcing guidelines weird
Because CSS doesn't lend itself to any enforcement. All the tools that appear around it includng those you like like SASS and "some processing" don't appear because people don't understand something or can't enforce something.
> Of course, all this is under the hindsight knowledge that HTML and CSS have evolved in recent years to be truly powerful and versatile.
Indeed. And many of these features have been made available across all major browsers only in the past two or so years.
No one is going to rewrite everything from SASS or CSS-in-JS just because some features now exist in vanilla CSS.
CSS modules are really enough - there's no need to overthink it any further.
I'm currently in a project where my first task, spanning several months, was to clean up after the previous guy. The main issues in styling were misguided attempts at sharing styles implemented via breaking encapsulation.
Should they be allergic to punishing people? No, but it be how it do.
It's worse than that. All the hype in design has been about creating a global design language, and enforcing it over all your teams for more than a decade now. All the hype has been on centralizing the design team, moving it away from the developers for some years. All the hype has been on tools that claim to enable reusing and distributing that work...
And yet everything is done in a way that developers have to do everything themselves and don't get to coordinate with each other.
(Honestly, I'm settling on the opinion that non-developing application design is a scam all around.)
> Consider the humble modal dialog. The web has <dialog>, a native element with built-in functionality. [...] Now observe what gets taught in tutorials, bootcamps, and popular React courses: build a modal with <div> elements
The dialog element is new! Only broadly supported since 2022. I find it hard to fault existing material not using it. Things like dialog, or a better select, are notable because they were lacking for so long.
filter and map immediately tell me what the purpose of the loops are - filter things and transform things. A for loop does not do this.
To someone familiar with functional programming these are very normal and easier to read and grep than just a loop. In other words filter and map give additional context as to the intent of the loop, a bare for loop does not.
Not to mention this is not abnormal in languages outside of JS, even non-functional ones.
That said Ive seen too many convoluted uses of reduce that I just avoid it out of principle.
If you really need arbitrary computation, I'm not sure there's any real readability benefit to reduce() over local mutation (emphasis on local!). Sure, there's immutability and possibly some benefit to that if you want to prove your code is correct, but that's usually pretty marginal.
I like comprehensions as well - and their syntax is quite readable - but I’d like for the two to be more at parity.
The fact that you single out .reduce() here is really telling to me. .reduce() definitely has a learning curve to it, but once you're used to it the resulting code is generally much simpler and the immutability of it is much less error-prone. I personally expect JS devs to be on the far side of that learning curve, but there's always a debate about what it's reasonable to expect.
That said, it's easy to get carried away, and some devs certainly do. I used to be one of those devs, but these days I sometimes just suck it up and use a local variable or two in a loop when the intent is perfectly clear and it's not leaking side effects outside of a narrow scope. But I'll be damned if I let anyone tell me to make imperative loops my only style or even my primary one.
Not only that, but the words that GP uses to single out .reduce() start with:
> I see so much convoluted code with arr.reduce() or many chained arr.map().filter().filter().map()
Which I do not doubt, but the point is diminished when one understands that a mapping of a filtering of a filtering of a mapping is itself a convoluted reduction. Just say that you prefer to read for-statements.
Only if that's how you were taught. For devs brought up in the FP manner, the for loop is as hard to grok as you find the chained stuff.
The Blub Paradox in action.
Plenty of them don’t like imperative loops, sure. But I’ve never seen someone assert that a simple loop is not intelligible to them while chaining functions are.
The real obsession with FP is the pervasive use of immutable state. But in apps, state must be effectively mutated, but now with a copy of the original immutable object. The FP obsession believes that this better than directly mutating the state each and every time.
Also weak typing plays into this. Some operations not immediately raising an error when the wrong type of thing is provided, is a recipe for bad experience when having point free style or semi point free style. If given the choice, at least one thing would be good to have, strong typing or static typing.
It takes eternal vigilance to maintain it. Refactoring it is a huge feat. And when you want to modify or add a single component, it doesn't make sense to re-cred in the whole CSS apparatus, and it's not necessarily trivial to figure out where to make the CSS change, so CSS files tend to become append-only.
It's like how learning how to "write clean code" doesn't really change much about how hard it is to change large software systems over time in a large team.
arr.map().filter().filter().map()
Every step on that is a modular operation. const x = arr.map()
const y = x.filter()
const z = y.filter()
const d = z.map()
now I can easily do this: const x = (arr) => arr.map()
const y = (arr) => arr.filter()
const z = (arr) => arr.filter()
const d = (arr) => arr.map()
o = d(z(y(x(arr))))
This is the main reason why functional programming is elegant. It makes every corner of your program modular and solves the fundamental program of program organization and modularity.with a for loop it is easier to read initially but if that filter chain grows the functional approach will begin to be better.
Take an example of a for loop doing the same thing:
const acc = []
for (const x in arr) {
y = map1(x)
if(filter(y) {
continue
}
z = y
if(filter(z)) {
continue
}
d = map2(z)
acc.push(d)
}
Yes more readable but imagine if the logic gets even more complex. Then at scale functional will become more readable AND more modular. Modularity, however, is the main problem here. You cannot split or modularize that for loop.But I agree with you. Initially and not at scale, functional programming loses readability. Functional programming is harder to read because people think procedurally over functionally. We like numbered instructions or a todo list, not a series of composed function calls and thus from a general view point functional programming is harder to read than procedural programming. But this has never been the benefit of functional.
Functional programming is supposed to solve the modularity problem with how you organize programs to be reusable. Functional programming objectively does this while it is only subjectively considered less readable by the majority of people.
My actual problem is with bad reduce functions that try to shoehorn multiple things into one function and often break the core concept of functional programming by mutating the input arguments. It's that code that would just be better written as a normal loop.
Making it read as a list of steps, helps me understand what it's doing a little easier than that long chain.
What you are expressing is that they are more familiar to you.
Nothing about map or filter makes them any more complex than a for loop, nothing.
At best they can be less familiar to a class of programmers.
It's interesting; when I'm writing JS in an actual .js file, I do tend to use for loops. But when I'm writing JS at the (CLI or browser-dev-console) REPL, I'll always reach for arr.map().filter().filter().map().
When you're at a REPL, with its single-line/statement, expression-oriented evaluation, it's always easier / more intuitive to "build up" an expression by taking the last expression from history, adding one more transformation, and then evaluating the result again to see what it looks like now. And you don't really want side-effects, because you want to be able to keep testing the same original line vis-a-vis those modifications without "corrupting" any of the variables it's referencing. So REPL coding leads naturally to building up expressions as sequences pure-FP transforms.
Whereas, when you're iteratively modifying and running a script, with everything getting re-evaluated from the start each time, it's not only more intuitive to assign intermediate results to variables, but also "free" to have side-effect-y code that updates those variables. It's that context where for loops become more natural.
I bet that a lot of the chained-expression JS code you see out there was originally built up at a REPL, and then pasted into a file, without then "rephrasing" it to be more readable/maintainable in the context of a code file.
...though there's also just FP programmers who write JS (or more often TS) as if it were a pure-FP language — either literally making every variable const / using Object.freeze / using Readonly<T>, or just writing all their code as if they had done that — where for loops become almost irrelevant, because they're only really for plain iteration (they're not list comprehensions), and plain iteration has few uses if you're not going to do anything that produces side-effects.
Leaving aside details, it seems clear the javascript frameworks were ahead of the platform and had the perverse incentive of staying that way, which also took the pressure off browser developers (already slowed by inter/standards politics), resulting in a long historical period of dissonance.
By comparison, Java bytecode gave Scala and Kotlin instant access to the enterprise, and the JDK team responded with faster cycles and steady but measured incorporation of features, because they realized they needed something to get enterprise off Java 1.8 and into licensable VM's. Progress is slower in the enterprise, but the dissonance seems minimal.
The developers look for ergonomics in maintaining the code base, that can scale to larger team and websites.
This requires a lot of customers JS framework code to offer, but in a sense, it's because the platform doesn't natively support it no.
Would there be ways to evolve the web platform to better align with the React style for example?
Here's my take:
- The web was visualized as a way to publish academic documents in a hyperlinked document system. Librarians and academics live in this world. We hear the word "semantic" from them a lot.
- The visual web was visualized as a way to publish documents that had a precise look. Graphic designers live in this world. They don't care about semantics. They do care about pixel perfect layouts and cool effects.
- The web app was visualized as a way to deliver software to users with lazy, "click link to install"-like behavior. What this crowd cares about is providing server functionality to users, and other concerns like semantics or pixel perfect are often secondary.
- The single page web app is also visualized as as a way to deliver software to users with lazy, "click link to install"-like behavior. They differ from the web app group in that they try to have more server functionality right in the client. Again semantics and pixel perfect are secondary. App complexity is a big problem that this group contends with, and this is what the article discusses.
Given these different ways of visualizing the web (and I'm sure I've left a few out), it's no wonder that we're stuck with the mess that is today's web development. The right solution is a sensible runtime for app development, that doesn't force you to render UI through the DOM and doesn't make it hard for you to get access to basic things like the local file system. We've known this forever (anyone remember Flash?).
WASM feels like it might finally allow app developers to do all of the software things that native platform developers get to do easily, and with the added bonus of strong sandboxing. It's early days yet, I think the "Ruby on Rails"-moment has not yet arrived there yet, i.e. a very popular, easy way for devs to create whatever app-de-jour everyone's excited about.
But WASM is weirdly allergic to the DOM and to javascript. The core, fundamental interfaces are terrible and highly disputed, so it will be restricted to mainly-WASM apps for some time.
Firstly, you can’t break what is already there, so any evolution of the general platform often has to make wider guarantees than a single framework.
Adopting ideas from any single framework too quickly may put you in a worse position. A framework can evolve and choose when to break compatibility, a language or platform standard has a tougher job in that regard.
Some things that front end frameworks have settled on are now being looked at for standardisation, but I’m personally still wary about changing something like the ecmascript for. It would be an easier call if there were a standard library which simply needed an implementation, but we aren’t quite there yet.
Because standards committees are also made out of people. They have their own agendas, experiences, biases, company loyalties etc.
And in the past ten or more years all standards have been taken over by a very small number of very prolific people from Google. Prolific as in: writing dozens and hundreds of specs and having their fingers in all standards.
The web had a golden age where it was understandable. The browsers' inconsistencies were mostly ironed out and you had references for what worked where. Flexbox was magic and you had shims to get it working where it wasn't yet supported. Web development was actually fun. When you solved problems, you were actually learning something, not just throwing stuff at the wall.
These web platforms gradually threw all that out. If you're a new engineer, realize that platform knowledge isn't real knowledge. Knowing React really well isn't a thing you can build a lasting career on. Your job is your job, but your education is your responsibility. If you don't want to be locked into an ecosystem, make sure you are constantly learning fundamentals.
> CSS cascades globally by design. Styles defined in one place affect elements everywhere, creating emergent patterns through specificity and inheritance.
I can’t be the only person who finds that this makes complex sites a nightmare to design? (Raise your hand if you’ve had to use `!important`)
So, is HTMX any good? Do people use it?
The primary mechanism of HTMX seems to be that you click on something, which causes the server to return HTML which is then shoved into the current HTML at the selected point. Is that a good primitive?
ORM keeps sucking because RDBMSes simply don't operate on objects. You can either impose ill conceived object models on the database, or try and fail to capture the database's affordances in objects.
I don't have a theory on what's gone wrong with front-end development to cause its chronic disease, but it may simply be that HTML is a document markup language, and makes a questionable base for a user interface.
No it is a hypertext [1] markup language and it doesn't suck if you actually use it as such.
The problem is that we decided that <a> and <form> tags should be the only hypercontrols, only triggered by clicking on them. However, libraries like HTMX and Unpoly enhance HTML so that every element can act as a hypercontrol, responding to all kinds of events. This allows you to implement interactive features such as autocompletion, form validation and infinite scrolling without writing a single line of JavaScript.
> ORM keeps sucking because RDBMSes simply don't operate on objects
Have you read [2]? Modern SQL can return nested JSON. No ORM needed.
[1] https://htmx.org/essays/how-did-rest-come-to-mean-the-opposi...
[2] https://www.scattered-thoughts.net/writing/sql-needed-struct...
The entire thing about users interacting with data through computers is one of them. There are several inconsistent goals, like consistency is at odds with flexibility, and accessibility is at odds with security.
We have those tools that push the incoherence around, but they are always prone to collide somewhere.
I also realise how weird it must be writing a personal blog article and then have random internet-folk dissecting it. Hope this doesn't dissuade any future articles.
There's a mismatch between how components are organized and how data flows through the front end application.
HTML is hierarchical. You have elements wrapping each other
A
└─ B
├─ C
│ ├─ C1
│ ├─ C2
│ │ ├─ C2a
│ │ └─ C2b
│ └─ C3
└─ D
├─ D1
├─ D2
│ ├─ D2a
│ └─ D2b
└─ D3
The issue here is that you can have something like D3 changing the state of an element like C3. So data must be artificially grafted on the above hierarchy to make it work.D3 wants to send data to C3? Well then that means B must be the owner of the data. So data that feels like it must live on D3 well now you got to make it live on B which has nothing to do really with D3.
This has one primary problem.
It destroys modularity and much of the benefits of functional programming making it sort of useless in react. If you did prop drilling most things that have a path from B to C3 and B to D3 now are no longer modular and can ONLY be used in context of data related to D3. If you didn't use prop drilling and used some contextual way to pass the data the problem is still there because now components on that path have access to contextual data that's irrelevant. I mean for a time it may be safe... but some developer may break it and have a component access that context and suddenly modularity is broken... that component can now never be moved outside of the context.
Really... how data flows through your program is actually a separate graph structure, and how HTML elements are organized in your program is another separate graph structure we try to graft into the first one and that creates a mess.
I think the best way to do this, hasn't been done yet. But really side effects like clicks and button presses should NOT live on components. Components don't know about these things. That keeps the hierarchy and modularity of these html elements pure. A framework that's well designed needs to enforce this separation. I even think this can be done in a way that is STILL functional, but not react.
HTML and jquery were sort of going in this direction, but the problem was HTML lacked modularization because you couldn't group tags into a component. You also have timing issues and complexity with state that jquery didn't handle.
Overall I think the old style was actually better. It was a better overall direction that had tons of problems and I think those problems could have been solved if we continued in that direction. AND i feel we could have continued this direction with some sort of functional framework.
Imagine something similar to jquery but this jquery can only directly reference components on your component hierarchy and pass data directly to components (aka manipulate them). Context and state will also live on this jquery like framework and it will be handled in a similar way to how react handles it. While components themselves can wrap other components, and parameters that go into components can ONLY have default values. The components can never dynamically "call" other components with data and actually have anything truly dynamic going on.... only the jquery like framework can do this.
It’s pretty great and hard to imagine going back to a React-like component hierarchy.
My team migrated away from Redux and for over a year I’ve been unable to pinpoint why I’ve been longing for it again. This has hit the nail on the head for me. Redux (and other “global” state management solutions) allowed me to decouple the data graph from the UI graph. When a UI event is triggered, an action is dispatched and then the data graph takes over. This makes the UI graph extremely simple - render UI and dispatch actions - but these days we’re mixing state with business logic, event handling, API calls, rendering UI, styling, etc. This is very much a problem of our own making that could be solved by a different architecture, but yeah… thanks for clarifying this feeling for me.
because any component that uses that state management solution becomes coupled to that global state. So let's say I want to use such a component in context of a completely different "global" state, then I can't do that anymore. Global state only saves you from prop drilling where you have to insert props into every parent element above the relevant element.
Believe it or not Jquery + html actually did the seperation of data flow and GUI much better than react.
The question is, how do I get re-useable GUI widgets that are decoupled from state all together such that every widget on my SPA can be reused anywhere on different global contexts and even on different apps. React does NOT solve this problem.
To avoid it, funny enough, you have to take cues from functional programming, and you need strong, composable, primitives with succint interfaces (the opposite of shadcn). Even for visually combining and placing components, you need stateless layout components [1] that don't depend on anything and just render children a certain way. This way it's possible to have a very flat component hierarchy. The entire state-logic at the top-level doesn't hurt much anymore. Also, just props+events are enough, no need for Context, Redux or other tricks.
But if you go the "Clean Code" route and the main reason to have components is so so they're not too big, then you get deep hierarchies, prop-drilling becomes a big problem, and you need Context or state libraries specifically for inter-component communication.
This is a problem that I've seen come up time and time again not only in Frontend but also in desktop apps and in video games. And backends often have the luxury of having the entire state in the database, so it doesn't happen as often.
Of course the side effect of this is that you will end up with bigger components, and you need better abstractions, but IMO this is a small price to pay for having code that is easier to work with and doesn't need special ways for handling the data. Also this method tends to work better in bigger apps and people complain it's wasteful in smaller apps, so there's that too.
[1] In the design world this is often called Templates. https://atomicdesign.bradfrost.com/chapter-2/#templates It's interesting how this didn't catch among frontend devs. I remember "layouts" being a big part of WPF for example.
With prop drilling how do you get the setter methods into C3? You have to prop drill all the way down.
Let's say I add a new component under C3 that has a side effect in some sister hierarchy under B. Then now suddenly in between and B and C3 and the other component I have to modify EVERYTHING to take that new change into account and these components now all have an extra prop that they don't intrinsically need but they keep in order to account for C3.
Because data is so intrinsically linked with components Every component between B and C3 now needs to be modified to prop drill when previously it wasn't needed.
when i use aurelia, then the whole page is one component and dynamic functionality in elements is achieved through bindings and functions that all share the data in the component. you can include other components, but you only do that where needed, and then passing data becomes a lot easier.
To me the major failing here was people over investing in faang thought leadership. Still haven't ever used react angular etc in a major project and gone wow this is great. JQuery got a lot closer. There were many other ideas coming forward around the time those frameworks came up, it was a fertileperiod for frontend. Sounds like we should cycle back.
I am not sure I agree “the state of front end development” is due to “functional purism” either. React just provided a good abstraction that worked well for many people. It’s a meritocracy. If the abstraction that worked best (however we measure that) had been something more OOP then that would have been adopted.