Although I understand what you saying about big shifts and kinda shared your opinion, specially about hooks, I started a new project from scratch last week and decided to give hooks a try (and, for reference, I've been using React on a daily basis for 5 years).
Oh boy I was wrong.
Hooks are way more easier and intuitive than what I thought. It makes code so much more readable and easier to reason about. Especially when before you had to wrap your components into x number of HOCs to inject the props your component needed, risking props collision, making it really hard to debug and use with TypeScript, and eventually making it a nightmare to understand and maintain your component. I'm not surprised that every React library added a hook API to its core.
At the end of the day it makes me more productive, so I guess that big shift was also a big win.
I've been through two large-scale projects now where we went 100% into hooks, and after a couple years the excitement has faded and I often wonder if it was worth it. It seems to end up making code even more complex than before, even though components look simpler on the surface. Not enough to go back to classes, but doesn't feel as good as those first steps. I absolutely hate the manual dependency tracking and having every single thing be wrapped in a useX() method to avoid unnecessary updates.
... having every single thing be wrapped in a useX() method to avoid unnecessary updates
That is a bit annoying, but it's significantly less annoying than having a single huge componentShouldUpdate function that deals with everything a complicated component uses.
Hooks don't really change the work a component does. The logic is essentially the same. They just break it down in to smaller, more reusable, more declarative units. That's usually nicer to work with and a lot more "React-y".
Is it really that bad? eslint-plugin-react-hooks installs by default when creating a project with create-react-app, which basically does the work for you.
In my opinion, the issue here is going 100%. There are use cases where having a class component is easier to manage, f.ex: if you make use of external classes.
Well-led projects tend to create new spin-offs rather than overextend their original idea. The former is riskier and more liable to fail, whereas the latter chases diminishing returns and undermines the conceptual integrity of the system.
Microsoft forcing Windows on mobile devices in the 2000s is an easy example of that kind of failed leadership.
I like hooks, but I've found them to be the biggest foot guns in my 20 year career.
Forget a dependency in the dependencies array, forget an useMemo or a useCallback and your application will rerender like crazy.
Yes, it looks nice from the point of view of understanding the code, but what actually happens at runtime and the amount of miss steps they can produce is astounding.
Nowadays I'm using Vue and it is way, way easier to deal with due to the reactivity model.
> you had to wrap your components into x number of HOCs to inject the props your component needed, risking props collision, making it really hard to debug...
What kind of application are you developing? Do you have complex UI? Or just lots and lots of pages where you reuse components a lot?
The kind of app that has 60k LOC, dozen of pages, makes 15 fetches per page (e.g. a report with 5 charts and 3 series per chart), reuse data between pages to avoid remaking the same fetches, have 1 or 2 forms synchronized between every page (like dates and selects filters), save the filters into the URL query params to be able to share the report and restore it when you open/refresh the app, and so on.
You end up with code like this at the bottom of your files
Although it's probably not that common anymore (as OC said, practices changed), it was a common pattern a few years ago.
In the new project I started last week, I'm using only hooks/context and a few libraries like react-router (useHistory, useParams), react-query (useQuery, useMutation), react-hooks-form (useForm)… and it has been a breath of fresh air. react-query is definitely another "big shift", it completely changed the way I used to think about fetching data and centralize it. It's very good, I highly recommend to try it.
Your example would be considerably simpler if you had separation of concerns [1]. You have routing, data fetching, redux, visual components, all in that small snippet. Sadly React devs seem to do this all the time... its like they have never heard of MVC.
I suggest searching what each of those HOCs is doing. This code is not dealing with routing, fetching, managing state or rendering. It is merely "gluing" those things together. Doing that can be considered a "single concern".
Over-splitting things only makes them worse to read and understand, even though each sub-component is prettier to look at.
Also notice there's a disclaimer on your linked article here the author retracts the recommendation.
> Over-splitting things only makes them worse to read and understand, even though each sub-component is prettier to look at.
React is a UI technology, and only your view layer should know about the React stack. The model layer should be UI-technology agnostic (includes avoiding Redux etc.) and so should most of the controller layer. If something better than React comes along (and it will) only the view layer should need to be rewritten.
Once again, I suggest that you search what each of those HOCs is doing.
These functions are not part of what would be a controller or model layer in MVC. This code is not dealing with routing, fetching, managing state or rendering. They are merely gluing complex data coming from these other parts of the system into what is the view layer here. The logical separation still exists!
Sure OP could reimplement all of them by hand, but ultimately it doesn't matter: the code is still properly separated in the way you're mentioning. Libraries like those are free to have functions operating in multiple concerns, as long as the logical separation still exists, which it does.
You are still wrong. This portion of the UI in this case doesn't care about the routing mechanism itself, but it does care about the history, which page is the current, and it also wants to trigger route navigations. This is a navigational component after all!
If your app needs to show, say, a menu bar, you need those things. Maybe some apps don't, but most of them do.
How would you implement this communication in your ideal system?
Let's see:
This component lives around two or three levels down from the entry point of your React tree (the part which communicates with the router). Which way is simplest way to connect this to the router indirectly? One way is by passing values and callbacks via props, from layer to layer, until it reaches the menubar. This works and is completely isolated, you basically have plain-simple-Javascript-objects "injected" from the top level, or instances of an interface, if you're doing Typescript! This follows the I of SOLID: Interface Segregation Principle. It also provides you with the S, Separation of Concerns.
But the "prop-drilling" is way too cumbersome. Let's maybe use the Context API, which is a way of replacing prop-drilling? It's still properly isolated.
Congratulations, you just re-invented the wheel and re-implemented exactly what withRouter does.
Sure you could have your own homegrown interfaces. Or maybe you could have no menu at all in your app! It's your choice. But it doesn't matter: this is still following the rules you are calling for.
The snippet illustrates exactly the concepts described in the article you linked.
And you are talking about MVC but that's kinda what is going on here: a bunch of smart components are injecting data ("model") and methods ("controller") into a dumb component ("view"), which displays them and calls them when some events are triggered (e.g. mount, change, clicks, submit, etc).
Yeah this is true, everyone doesn't like change in the beginning. but once I started using hooks for a couple of weeks, it is amazing to see that my productivity is increased a lot
Oh boy I was wrong.
Hooks are way more easier and intuitive than what I thought. It makes code so much more readable and easier to reason about. Especially when before you had to wrap your components into x number of HOCs to inject the props your component needed, risking props collision, making it really hard to debug and use with TypeScript, and eventually making it a nightmare to understand and maintain your component. I'm not surprised that every React library added a hook API to its core.
At the end of the day it makes me more productive, so I guess that big shift was also a big win.