Controlled Components Make No Sense
Tags: webdev, react, javascript
Published: 2023-10-10 14:11:11

Before I learned what SPA meant and how to build SPAs I built websites using PHP, MySQL and Nginx. It was a simpler time for web development in general - just write some HTML and ship it to the user. Building frontends was simple - just slap together some HTML, CSS and JavaScript until the site looked like what the client asked for and behaved like it. All the hard parts were usually in the backend and even they weren't too hard because PHP comes with batteries included. If the end-user's browser didn't run the JavaScript you wrote it wasn't the end of the world; the website still worked most of the time. The most complex interactions were usually forms that looked really nice and these worked as long as you knew how to click the submit button and had an internet connection.

The first SPA framework I learned was AngularJS and I only learned it because the company I worked for at that time was using it. It took me some time to wrap my head around all the different concepts - controllers, modules, services, two-way data binding, etc. - but once I did it felt great building frontends. AngularJS made it feel trivial to build highly interactive UIs and clients liked the bling factor. It seemed like common sense that this is the way forward for the web development industry - highly interactive UIs made possible with megabytes of JavaScript.

After some time I heard about Angular. Much to everyone's confusion it seemed prudent to the Angular team that Angular 1.x should be called AngularJS and all future versions of Angular should be called Angular. To help simplify matters they made different domains for them - AngularJS is on [angularjs.org](https://angularjs.org) and Angular is on [angular.io](https://angular.io). Angular was not backwards compatible with AngularJS which meant that if you wanted to use Angular in an existing project you had to rewrite it. Not nice. Much of the concepts were the same but you now had to use TypeScript. It did come with a nice CLI however.

Now at this point you must be thinking, "I thought this article is about controlled components not making sense. This guy is just talking about Angular and AngularJS." Yes. We're getting there.

One great feature/footgun that came with AngularJS was two-way data binding and very quickly people found out that this feature/footgun is generally a bad idea - you couldn't tell who was changing the data and why and where. What in fact was a good idea is one-way data binding. The classic demo for Angular(JS)'s data-binding was to have a text input and whenever you type something into it the text would appear elsewhere on the page, possibly transformed in some way. This is nothing spectacular in and of itself - anybody can do the same with an event listener or two. What was amazing however was how little code we had to write to achieve it. Everyone fell in love with this.

Note that we've ALWAYS had one-way data binding - the browser keeps track of the state of form controls and we used them during `onchange` and/or `onsubmit`. We basically went back to where we came from.

Besides the conversation around Angular and AngularJS's (unfortunate) naming conventions there was another conversation going on - writing applications using Angular(JS) felt like writing applications in Java. Just so much of boilerplate! You had to have a module and a controller and a template and a bunch of other things before you could even print a 'hello, world!' message. Nobody liked that. This was fertile ground for a much more approachable framework. I mean library. Perhaps that's why they named it React - it was a React-ion to Angular(JS)'s boilerplate.

Well whatever the reason for calling it React, everyone did react. To JSX. It took some time for people to succumb to the Stockholm Syndrome but once they did everyone was talking about how great it was, since it brought the template and the behavior to the same place. And you could see all of it at the same time. Unless you're writing a dumb component and the smart component which actually has all the data(-fetching) logic is 5 levels up the component tree.

Since I was going into React from AngularJS it wasn't too hard. Most of the concepts were the same. What was new was the fact that React wrapped ALL the events the browser produced, the JSX produces React Elements and not HTML elements, and there was now a special way to do forms and form controls. Enter controlled components. At first glance you wouldn't think too much about it but after writing a few (dozen) `onChange` handlers you soon realize that the reason they exist is not because something is broken in HTML or browsers, it's because React has no other way to keep track of the form control's state. Yes you can use refs but that's a React anti-pattern afaik so people generally avoid it.

Forms are hard. Forms in React are painful. A lot of websites are simple forms that collect some data and do something with it. Even Google is essentially just a (small) form. An app I was writing for my previous company was just a very large form. Forms are everywhere and in my, humble, opinion I think that something as common as forms shouldn't be painful. And in my other, humble, opinion a sure fire way to make forms painful is to manage the form's state manually when the browser is already capable of doing it. Just think about that for a moment. Why are we manually managing form state when the browser can already do it, and in fact has been doing it for decades?

React/Modern web development has gone through many phases:
- Class components are great.
- Class components are terrible.
- Shipping the entire React codebase for just that one interactive form in our website is fine.
- Actually we should lazy load components and other dependencies.
- Hooks are great.
- `onEffect` is the bane of our React applications.
- Let's render our React applications twice - once in the server and once in the browser - for that sweet, sweet first contentful paint.

Notice the last point. Doesn't that sound a lot like writing applications with PHP, MySQL and Nginx? Write some JSX, render it to HTML and ship the HTML to the user.

I think it's safe to say that just like how we went back to one-way data binding and how we're going back to the server just returning HTML, we should probably just go back to letting the browser handle form state for us.

You don't need React to build forms; you need HTML.

If you really must have interactivity in your website just use [Alpine](https://alpinejs.dev).

And if you hate page reloads just use [HTMX](https://htmx.org).

You'll soon notice that your websites are still largely useable even if JavaScript is disabled.

Fun fact: Google, who made Angular, don't use it as much.