Can We All Just Admit React Hooks Were a Bad Idea?
medium.com
medium.com
Also, HOCs were really bad at SOLID too. They replaced inheritance with composition. They rarely supported Liskov Substition. Their dependency injection was often in the wrong places. They spit out regular dumb JS OO classes and object instances so their supposed object-oriented-ness wasn't directly in question, and that really was the only thing to commend them on.
That's a good tradeoff in statically typed language. I'll take composition over inheritance and abstract out the interfaces I want to maintain, rather than worrying about who has implicitly started to depend on an incidental interface.
This is yet-another-example of why JS has a lot of growing left to do. If a language doesn't require you to define types (and enforce them), you get these messy dead-ends that are used to rationalize even worse solutions.
Those APIs are "weird" to traditional OO developers and OO purists, but those APIs are designed for static typing and accountability to type systems (Typescript especially now).
In contrast, hooks work much better from a typed perspective, but also from the perspective of providing composition over inheritance - it's much easier to compose multiple hooks to create new functionality than it was to compose multiple HOCs.
I agree. However, while hooks solved one problem that you illustrated, it created another one: more and more business logic is tied directly to component lifecycles.
Further, in order to write tests we have one good tool: integration testing by setting up a mock API. I'm not saying that's necessarily bad but it's quite a huge lift as compared to passing props into a component and seeing what happens.
As other commenters point out the lament that Redux has mostly fallen out of fashion, where my own experience points out that Redux is still incredibly useful post-Hooks. (For testability, for separating concerns in clear ways in a codebase, and even for debug-time tooling.) Hooks are good at what they do, but what they do should probably not be your entire business logic of your app. Striking that balance is hard, different teams have different ideas and ideals. It may take a bit before "the best pattern" emerges from all this.
Sometimes I wish post-hooks that there was a good way again to spot "pure" components in the wild (entirely prop-driven with no internal state). Obviously, pre-hooks this was sometimes very easy to see in a codebase "pure" components were most often written as functions because functions had to be "pure" and stateful functions had to be written as component classes. Maybe less obviously that wasn't always a clean distinction either: there were plenty of pure components written class-style simply for habit, and there have always been ways in JS to sneak side effects into even "pure" functions, but it was a good enough first-order approximation that a lot of people relied on it, including myself. I don't know if there is a good way to mark "pure" React components today in a similar first-order approximation other than in documentation, though documentation is a good place to start and most libraries aren't currently doing even that and about the only reliable test is to grep /[\b]use/ through your project, and even that has some issues with not every hook follows the "useSomething" naming pattern, just most of them. (And the false positives that "useSomething" may still be the best name for even a non-hook function.) I don't have a good answer here, but I do understand it as a pain point of hook usage, and one that is a low level bother to myself too.
The other issues people had like boilerplate and massive reducer files I think were solvable with various conventions or tools, but forcing an interface between unidirectional data and request-response seemed like an intractable problem.
I've wondered for awhile if there could have been some way of extending the unidirectional flow all the way back to the server in a way that could solve this mismatch in a cleaner manner.
I don't care what Dan says about his own project, he's demonstrably wrong.
The more I do web development, the more I appreciate the wisdom of no client state.
> The reason people started embracing functional languages was that, for the most part, they did away with state in favor of just passing functions everywhere.
No no no. They did not 'do away with state'. Handling state became _explicit_ rather than implicit. Haskell, for example, has _tons_ of different ways of handling state depending on the specific situation. State is a key feature of React, and the question is not whether to have it or not, but how to support it.
> OK, I just spent several paragraphs explaining why SOLID principles apply
No, you didn't. You explained to someone that already cares about SOLID that they can apply that thinking here. But didn't explain why I, someone that doesn't care about that acronym, should care in this or any other case.
> People complain about all the boilerplate in Redux mapStateToProps/mapDispatchToProps, but what that boilerplate does is give you a specific place to put the dependencies that is outside the View, so that the View can focus on rendering the Model and capturing user input, without worrying about saving user input back to the Model or exactly where in the Model the data to display lives.
A react component is a view function with some effects + local state. Whether you represent that with a class or a function + hooks doesn't change that. It's just a different set of tools. The principles don't change. You can architect your view function however you like.
> But you don’t gain anything from that hiding, because you have to read the code of the hook to understand what arguments to pass it and what it returns.
This is...just how programming works? Functions typically have documentation describing inputs and outputs.
> Now, let’s implement isAnyOf like a component with hooks
You're taking a simple function and turning it into a...component? What does that even mean? What dependencies are you talking about? What is the point of all this? It's just a higher order function.
> There’s a direct dependency running from App -> useDataApi, because App is directly reaching out and grabbing that dependency. There is no way to swap that out for a different implementation.
The component is a function. You can pass things into a function! You learn this in your first week of programming. What is the insight here? Why make this sound so complicated?
> And hooks set people up to create problems for themselves, in part because they encourage violation of those principles.
You haven't shown this at all. How do functions discourage abstraction? It's a choice by the programmer!
lol, this is just a problem with developers in general. Between burnout and hiring bias they trend young. Information and best practices struggle to get passed down across short generational overlaps.
The number of times I have seen the same bad ideas resurface over and over in my twenty year career is just insane. Document stores were the rage, then they were bad, then they were great again, and I think we’re cooling down on them again.
As an example, I find the idea of deno really ludicruous. As if the ecosystem wasn't chaotic enough, now there is a new runtime, and of course it is not compatible with the current standard. While it's a pretty cool and inventive piece of work in itself, the idea of having to rewrite all of userland should frighten the community. People should challenge its need and value and push the community to find alternatives. People should respect the time and effort it takes to reach maturity. But now we now have a rewrite of node dotenv in deno dotenv, oak middleware inspired by koa, a new semver package, a new base64 package, yet another modern web framework... and what not. I predict, we will witness a race within the community to be the first to rewrite popular packages, node packages will slowly get abandoned, companies will open positions for deno developers, there will be a deno conf and new developers will be asking whether they should take the node course or the deno course.
The good news is that in this case the "competition" seems healthy: Deno's efforts have spurred Node forward to better embracing standards they've notoriously avoided before (or at least have been slow to adapt to). Deno's efforts have helped make Node more solid in its ESM support, as a big for instance. There's still an optimism that Deno and Node will again converge sometime down the future.
Fwiw, I'm very glad that Linus himself hasn't taken your attitude to new technology, and is quite happy exploring experimental ideas, such as the new Rust-in-Linux work that's going on right now.