Hooks are the worst thing to ever happen to React. They're so easy to get started with but every codebase I've seen adopt them has turned into complete untestable spaghetti.
Mixing stateful effectful code inside what would otherwise be a pure declarative render function leads to so much complexity in an attempt to bridge the two paradigms. Junior engineers are also constantly tripped up by the subtleties of `useEffect` and `useState`.
Furthermore I think attempting to "encapsulate" side effects is a bad approach to writing testable programs. Most React components that use hooks end up having several chains of promises inside them but no way to await the final promise meaning tests have to be full of
await wait(0)
await wait(0)
await wait(0)
God forbid someone comes along and tries to be clever and replaces it with
await wait(3)
which now makes the test non-deterministic.
Cycle.js is the only framework that seems to get this right by acknowledging that a component doesn't just output JSX, but actually outputs JSX, HTTP Requests, etc.
Look, I get that Redux was a pain in the ass with all the boilerplate, but how did we throw the baby (unidirectional data flow) out with the bathwater. I no longer advertise that I know frontend development because the entire react community seems to have lost its mind.
Is there anybody that develops on the frontend professionally fulltime and makes these kinds of complaints?
It seems like these are always drawn up by backend developers who lob fistfuls of aggression-poop over the fence for reasons beyond my fathoming, or junior developers (or UI folks who occasionally use javascript) that find it easier to trash on patterns rather than learn about them.
I have used React professionally for years and get the skepticism around hooks. It seemed really stupid to me before I learned about it. Why change what wasn't broken?
Now after learning about it I'll never go back to class components if it can be avoided and happily recommend hooks to all my clients who also end up loving them.
No idea what this promise chaining thing is that you're talking about. Sounds like a bad design pattern that has nothing to do with hooks. No idea why you're suggesting Redux has anything to do with hooks. They're completely different.
This meme is dead. Only a superficial understanding of React, or ingrained bad programming pattern habits, keep this meme alive.
I've more or less driven frontend development with React at several companies over the last 4 years. While some of the complaints are exaggerated I don't think the overarching premise should be dismissed as "a meme."
> No idea what this promise chaining thing is that you're talking about.
If you have any asynchronous things going on in `useEffect`, you'll have to do something similar to that `await(0)` song and dance in tests. This specifically affects tests if you do things like update the UI by toggling loading spinners on await.
> Redux
s/Redux/higher order components. One of the motivations for hooks was that as a mechanism for logic composition, HOCs just felt awful to use. (So did render props, which everyone suddenly used for everything in a brief moment of collective insanity.)
> Only a superficial understanding of React
I think there's something in this. The fact is that good or bad, 1) hooks aren't intuitive, 2) hooks have basically doubled React's API surface area. Previously, React was so simple that a backend engineer could pick it up and get productive with it in half a week. That's much less the case these days. I've been onboarding devs to React for years, and these days there's a lot more "yeah, that's magic, you don't need to know how that works for now."
> If you have any asynchronous things going on in `useEffect`, you'll have to do something similar to that `await(0)` song and dance in tests. This specifically affects tests if you do things like update the UI by toggling loading spinners on await.
You should have a core (pure) functional component, that doesn't perform network requests, and an outer component for side effects.
Your testing for the inner component becomes trivial: Do the correct props product the correct HTML? Fairly trivial to validate. Use Enzyme, react-testing-library, etc.
The outer component, which contains side effects, can be covered through an end-to-end testing tool like Cypress.
> You should have a core (pure) functional component, that doesn't perform network requests, and an outer component for side effects
IMO, this is the biggest thing we lost with hooks. Before there were two different types of component, and you couldn't make a pure functional component stateful or effectful without completely rewriting it.
I love hooks, but I'm constantly asking in code reviews "are you sure this is where you want this state to live?".
The other big problem with hooks is that there are no safeguards (and little documentation) about using them wrong. The post you're responding to mentions using `useEffect` to toggle a loading spinner. This might make sense if the loading spinner is outside the React component tree, but if you're using useEffect to toggle some piece of React state, yer doin it wrong. Once you start slipping these kinds of hacks into a codebase, it's a slippery slope.
This is true as long as you're able to strictly enforce the container-presenter pattern, which is increasingly harder to do as an application grows in complexity.
You should have a core (pure) functional component, that doesn't perform network requests, and an outer component for side effects.
Except when you go to use that container component inside another component. Then you’re back to the same problem. Unless you’re advocating for a single container component at the top level in which case we completely agree!
> Previously, React was so simple that a backend engineer could pick it up and get productive with it in half a week.
With hooks, I've literally seen almost completely novice programmers (e.g., people who’d hacked out some powershell while working desktop support previously) pick up React and be productive in a month or so, while also learning JS and Python and SQL and backend development from scratch in parallel; if backend “engineers” can't do it in half a week or so, they’re hacks.
Yup, I use React professionally for four years now with some really big applications (10ks LOC) and I would always opt for class components.
The hooks API not only suffer from weird wording (how is useEffect more expressive than componentDidMount) but it introduced some concepts like dependency declarations (again useEffect) and a new meta syntax.
It could have been a good development if the published preview would have been a real request for comments with the possibility of changing something. But instead it was introduced as stable in basically the same form.
> how is useEffect more expressive than componentDidMount
Well, useEffect cross-cuts way more of the component lifecycle than just componentDidMount: it also handles componentDidUpdate and componentWillUnmount, and a few other things that didn't quite get lifecycle functions before. I suppose you could call it useWhenComponentMountsUpdatesOrWillUnmount, but "effect" as in "side effect" for when the component moves through lifecycle events isn't an uncommon name for that sort of thing (even outside of React).
Is there anybody that develops on the frontend professionally fulltime and makes these kinds of complaints?
I do? These are all real problems I've encountered working on large apps at multiple organizations. React hooks are a constant source of difficult to write and non-deterministic tests. Class components also suffer from the same problems.
Now after learning about it I'll never go back to class components.
Those aren't the only two options. What I'm advocating for is using react only for pure functional components without the use of any hooks. Hooks are completely isomorphic to class components. Instead of binding the `this` of class methods to an object as a class component would. React hooks maintain basically the same thing in the background and bind it to the values of the hooks, identifying them by call order in your component. Which is why you can't call them conditionally. They're way nicer than class lifecycle methods. Primarily because they organize code that's related together, rather than by when in the lifeCycle it is triggered.
IMO state and side effects belong outside of the render functions. Not mixed up inside them. This is exactly what redux, cycles.js, Elm, MVC, etc do.
No idea what this promise chaining thing is that you're talking about.
You know that async await just chains promises together right? e.g.
So any callback that makes multiple API calls will require multiple `await wait(0)` in tests to allow for the mocked API calls to resolve before the UI will be finished updating.
No idea why you're suggesting Redux has anything to do with hooks. They're completely different.
If you can't see why Redux is related to hooks, I don't know what to say. They're both approaches to managing the impure parts of your UI. I suppose you could continue to insult my experience as a frontend engineer.
I agree, the main issue that I’ve had is just that its easy for (usually junior) devs to accidentally make infinite loops with interdependent state + effect hooks. But besides that I would never go back to class components.
I haven't used React and React hooks as long as I have used Java and Maven, but consider this:
If many successful people voluntarily use something you cannot understand there is always the possibility that you are the one who are missing out on something.
I absolutely acknowledge this is a possibility, but I think I do understand why people use it. Because they're easy, hooks are just so god damn easy to use. Want some state? Just chuck in a `useState` and call it day. But the problem is, they're not simple, and that complexity will grow and grow until it makes the app unmaintainable. I've seen this happen at three different companies I've worked at.
To me the main benefit isn't that they're easy, it's that they're composable. They allow you to reuse "business logic" in multiple components. If it's getting too complex then that's presumably when you ought to be creating a custom hook which wraps up some of that complexity.
I'm not familiar with react hooks, but when I read what you write, my immediate reaction is: we already have a way to reuse business logic. It's called functions. Define a function and use it, either a global static function or pass it down to the component if you want to mock it.
Reading the react docs, I find this part interesting:
> Hooks were designed with static typing in mind. Because they’re functions, they are easier to type correctly than patterns like higher-order components. Importantly, custom Hooks give you the power to constrain React API if you’d like to type them more strictly in some way. React gives you the primitives, but you can combine them in different ways than what we provide out of the box.
Seems to me as if hooks try to fix the lack of a good programming language. These approaches usually go wrong eventually. Maybe it's time to ditch Javascript (and even Typescript) and use a proper language (purescript maybe?) so that all these things can be done with just functions.
> I'm not familiar with react hooks, but when I read what you write, my immediate reaction is: we already have a way to reuse business logic. It's called functions.
Hooks are functions. If that wasn't obvious, it's also explicit called out as a key thing about them in the excerpt from the docs that you present and then try to make a point with that entirely ignores the content of the excerpt.
One key difference between hooks and functions is that hooks tap into some hidden global component registry (I'm being handwavey here but this is more or less what happens). This allows side effects to be persisted and tracked between function calls.
You can probably build all this infrastructure yourself without hooks and only with plain functions. It'll involve some global store and some subscription mechanism. You'll end up with something really similar to Redux.
One thing you are missing is a lot of organizations don’t care about UI tests. They’re brittle and the UI is always changing anyways so you get diminishing returns from writing many of them. Usually an end to end test is enough. UI logic is usually not that complicated, if it doesn’t work you will know right away. Hooks are acceptable.
If you use a linter though you can just auto enforce all the rules and back to it being just fine. An extra setup step but tools like create react app bundle ESLint for you already so I don’t mind it.
I actually think the problem is a lack of critical thinking. When Hooks were announced by Dan Abramov, people were hailing him as a genius etc, that this was a panacea.
Junior engineers are also constantly tripped up by the subtleties of `useEffect` and `useState`.
They are, but they're also tripped up by the subtleties of class-based React components. The difference is that with hooks it's really obvious when they've tripped up (because React can tell, and warns you) while with classes it's really not obvious at all. This is one place where hooks are a clear win.
Hooks are not some panacea that's going to fix a defunct development process or poor code organization and management.
Any codebase, using any language, framework, feature, and technology, needs to be managed properly. There's really no getting around that. And without that, you're almost always going to end up with things turning into something along the lines of "complete untestable spaghetti". Especially when you have junior developers involved. Hooks are no different, nor is the entire Javascript landscape.
In general these complaints are getting tiring and often feel like they are coming from folks who don't have the appropriate level of experience or knowledge in a particular subject area to be making these generalizations. I don't know what your level of competence is, but this line:
> Look, I get that Redux was a pain in the ass with all the boilerplate, but how did we throw the baby (unidirectional data flow) out with the bathwater.
gives me pause, because Hooks aren't meant to replace Redux in any way. There are hooks that maintain state, sure, but generally they're meant to replace class component lifecycle functions. Hooks and Redux still coexist quite well.
I think hooks and effects are really good. You can test them separately. You can write global reactions to state changes very easily.
I find hooks/effects better than mobx or redux. hooks/effects, redux, mobx would be my preference order. Although redux works so well that it can be used with hooks/effects.
For global state I would just use a React.Context with it's own effects. Most components do not have any async effects.
I do admit that when jumping into a new codebase with hooks/effects I often find people have just butchered the concept. But I find that when I jump into codebases with mobx and redux too.
The butchering that people do with mobx is much worse, with massive module importing of global stores, misuse of `name?: type` inside stores because they want to initialize the store at a later time.
Yep. I've watched the video that @BreakfastB0b referenced and must say I do not see how this can't be done with new hooks/effects.
I can easily have all the user settings accessed inside a small effect and react to events or not depending on the settings and it's composable and reusable.
Testing with jest/enzyme is still the same. I'm writing selectors, simulating clicks and the component rerenders. Mocking the effects or functions that effects use is also simple. With jest 26 and modern fake timers it's easy to test async effects and changes of state too.
I guess the only flaw of hooks/effects is that they not efficient because of so many effects fetching the same state but they are efficient enough. For example, mobx would be extremely efficient in that regard (any store.value access is just that, not a useState call with new () => {} instantiated on every rerender.
You can write global reactions to state changes very easily.
This is exactly the problem, shared mutable state leads to "spooky action at a distance". It results in causal connections between parts of your codebase that are not reflected in the control flow of your code. If you have immutable unidirectional data flow causal relationships between parts of your code base must be reified in the control flow of the language.
Most components do not have any async effects.
At least when using Apollo / GraphQL almost every component ends up with async effects.
But the state is not mutable, the state is inside a React.Context that provides functions that manipulate it. It's practically the same as redux. You still need to have async actions with redux and use these actions.
mobx on the other hand is exactly that, the state is globally mutable, anyone can import it from anywhere to anywhere.
That's just the nature of being a junior engineer — you get tripped up. Tripped up by mutating objects, by stale closures, by comparing numbers to strings, by asynchrony, by thinking in terms of rxjs or xstream streams, by god knows what else.
Sure and if you are at a big company like Facebook that makes total sense. However lots of us work for smaller firms. My company has no junior engineers.
The problem I see with hooks is how they replace one set of problems with another, and demand you structure your code in a way to avoid those pitfalls. You know, how to structure conditional code, how to use loops when dealing with hooks, having to think about function object equality, having to supply the functions and variables your callback/effect closes over as dependencies (I still don't understand why you would do some of that in favour of pulling your callback out of the component)... and for what? For it all to change in another year or so when the JS community starts hankering for another paradigm?
Lately when working with all this I feel as if the same code would be simpler and easier to follow if it wasn't shoehorned into JS. It's like the uncanny valley of functional programming.
But more fundamentally, dealing with frontend application code is mentally exhausting in a way I haven't experienced before. I feel like I'm no longer learning Javascript as a language, but just keeping up with the novel abstraction of the month.
I should add that I don't hate it, and there's plenty still to appreciate. Some of it is a joy and it's refreshing to work on projects that embrace more functional styles over the typical CRUD and OOP taxonomy construction you get in a typical backend job. Different set of problems once you get away from the typical framework stuff (react, redux, saga boilerplate).
I feel like that's more a complaint about the JS frontend ecosystem as a whole. Every paradigm has its own pitfalls and idioms, and at least to me hooks seem better than class components most of the time. You exchange one set of pitfalls with another, and get more pleasant, more composable behavior overall. But compared to many GUI frameworks in other languages it still feels like a mess, and it's a mess that constantly changes from under you.
As someone who architects FE codebases for a living I both agree and disagree with you.
React hooks definitely threw a wrinkle in the testability of react components. However, the ideas around how the FE should be tested has changed to the point where current thinking is that integration tests are the most valuable tests you can write. Testing what a react component does in isolation of redux or its side-effects is easy to do and also not incredibly useful.
Furthermore, many modern codebases still use redux. I still advocate for it. Using hooks and redux are not mutually exclusive.
I wish there was something better than hooks, it’s very easy to forget a dependency or get into infinite loops, but they do make life much easier. No more mapStateToProps or mapDispatchToProps HoCs. No more “smart” vs “dumb” components. No more function/class components. There’s only one component.
Also, we are seeing an increase in “headless” react libraries, where the logic of some functionality is disconnected from the visual design of a set of components. It makes composition, extendability, and maintainability much easier.
Happy that someone other than me sees that too.
I predict that companies will start leaving React in 5 years. React will have the same fate as AngularJS and jQuery.
This isn't a "deep dive" in any sense of the word. Call it what it is: 'useRef tutorial'. If I'm opening up something called "deep dive" on a topic I don't know very well then I expect to learn at least something I don't already know from 5 minutes of reading the official docs.
Also, why even bother giving an example of useCallback if all the example does is log something to the console? Especially immediately following a section called "Real world use cases". This is how newbs get the mistaken idea that you need shit like useCallback to show and hide an element based on state and you end up with overengineered code everywhere.
Examples of library functionality should be of something that you should actually use that functionality for (because it's impossible or impractical to write in other ways). Not just because it makes the docs better, but because it gives you a chance to run into a big red flag when you've implemented something and realise you can't actually find a use case for it.
Then again half the people writing docs for libraries in React land don't even use the libraries they're documenting.
Well if I wasn’t already convinced that hooks were a bad idea, this cemented it for me. What a bunch of confusing complexity to mix up with something that otherwise looks like a function. Data in, data out, and mysterious side effects that look like functions too! Track them... with dev tools I guess? I’m sure they’re plenty testable... with mocks? I’ve spent a lot of time criticizing angular for implicit magic, but at least they’ve tried to make it seem like you can pass values and substitute things.
I still vastly prefer JSX for frontend dev but I think really that’s about all I care for in this ecosystem.
Hooks are the place to segment out effectful code from otherwise pure code
useRef is a hook to serve just that purpose as is useState and useEffect.
Building new hooks off of these base hooks lets you write your own custom hooks to handle whatever side effects you need.
The existence of a hook is what should alert you to the fact that there may be some side effect thing happening here.
Not sure if this applies to you but I've found that most people who criticize hooks haven't made the leap to using custom hooks and have instead only used ones provided to them (useState, useEffect, etc).
Hooks are an incredible way of encapsulating behavior, separating out that behavior from a component, and exposing that behavior to any component that needs the same behavior. Check out this video for a practical example: https://www.youtube.com/watch?v=nUzLlHFVXx0
I'm in the middle of a big refactor and writing reusable custom hooks are an absolute savior to share functionality across components that may have nothing to do with each other.
Hooks are functions, which is why we can compose them to build new behavior from smaller ones. That's the point. Just cause a thing is a function doesn't mean its pure. Like I said above, hooks are often meant to contain the impure parts of your otherwise declartive and pure UI expressing the UI as a function of state.
I'm fully on Team Hook, and convert class-based components whenever I touch them. Still, I feel that there are some serious compromises in the hooks approach and wonder if there wasn't some other potential future we missed out on.
What would it have looked like if class-based components got a v2 rev instead? I feel like a lot of the crazy with class-based components came from the myriad low-level callbacks. useEffect() is a better abstraction, but it seems like some sort of class-based analogue could have been created (perhaps registering effects in the constructor).
The other nice thing about hooks is composability. I haven't given it a ton of thought but maybe that could have been addressed somehow too? Dunno.
I would be willing to accept some extra verbosity if it meant we could relax or eliminate some of the rules of hooks. The conditional rendering problem sucks.
> What would it have looked like if class-based components got a v2 rev instead? I feel like a lot of the crazy with class-based components came from the myriad low-level callbacks. useEffect() is a better abstraction, but it seems like some sort of class-based analogue could have been created (perhaps registering effects in the constructor).
This certainly seems like it would have been a better design, it would definitely be easier to reason about. Or even just an additional function boundary between where hooks are called and where JSX is rendered (semantically it’s basically the same thing but less verbose). Or even just changing the hooks interface to be a factory, accepting a component function as a parameter, where that component receives props `P & HookState<FooHook>`, which probably would be a lighter lift for React’s implementation because it could simply be a wrapper around the current behavior.
You could trial implement some of these ideas as a design pattern in the existing Hooks design as essentially an "HOC".
interface HookClassComponent {
render(): React.JSX
}
interface HookClassComponentConstructor {
new(): HookClassComponent // all hooks should happen here
}
function renderHookClass(hookClass: HookClassComponentConstructor) {
const instance = new hookClass()
return hookClass.render()
}
That said, if you were to actually try to build HookClassComponents like this you'd see some of why React presumably didn't bother to include an "HOC" like this. A lot of the hooks rely on simple arrow function closures around variables and it would presumably be a lot easier to make mistakes in handling that temporary mutable state directly as class (private) properties than in single function scopes, and there would presumably be a lot more pressure to make such state modifiable by outside components.
I'd be curious though to see more people try an "HOC" like this, though, and report back what sort of results they get.
I’ve thought about wrapping it as a proof of concept when I have some space to work on it. But I’m more inclined to try out a factory style interface (outer function initializes hooks and returns a function component that accepts the props for the base component plus state from the hooks).
I think it would fit more my preferred style as well as the kind of interface that people find attractive in hooks. It would work well with generics in TypeScript. And it wouldn’t be as fussy about the weird implicit behavior of component naming that came with wrapped/generated classes and decorators in past experiments with composition in past React fads.
Hooks are the place to segment out effectful code from otherwise pure code
The effectful code shouldn't even be there. It should live separately from the render / view code to make it easier / faster to test without having to simulate a fake DOM. The average call to `render` takes around 100ms. The tests could be orders of magnitude faster and so much easier to test if people would just keep their business logic and effects seperate from their render code. Does no one remember MVC?
> The effectful code shouldn't even be there. It should live separately from the render / view code
If that's your preference React is probably the wrong tool to begin with. React is just the view in MVC. Plenty of frameworks exist to help build an MVC with react that provide their own structure and guidelines.
React is incredibly flexible and can be used in many ways. All this shows is the need for strong architecture decisions and guidance because React is so hands off in making recomendations of how it should be used or what tools it should be used with.
That's a strength. It absolutely requires more up front work on yours and mine part to construct an architecture that fits our needs, but it gives us the ability to completely tailor the solution which is the best part.
How is this and grandparent so heavily downvoted without proper replies? Seeing this a lot in this thread.
I feel like the unspoken difference in this thread is between people who have IO/business in their view code, and people whose taste is to strongly separate them, leaving component state for strict view modeling like animation timers, etc. I had this difference with my former tech lead.
What would you test with just a pure react component? What are you actually testing? Given some props it prints certain html?
I’d argue that test is not that useful. Instead what we do in the react world today is focus on integration tests. Testing what happens when you navigate to a page, click on elements, and make sure it does what it is supposed to.
The usefulness of testing a pure functional stateless component depends on the complexity of what the component does based on its props. And in some cases the testing is valuable but can be abstracted, e.g. with snapshot regression testing.
Just as you probably don’t get much value out of testing a function that increments an input number, but you may get a lot of value out of testing, say, a function that generates a series of smooth quadratic curves over a series of points.
And that’s a much easier thing to test at the unit level than integration.
It seems to me that either:
- your usage of props and rendering isn’t particularly complex, but your interaction model is
- you’re using state to manage that complexity where someone else may model it without state
I’m not passing judgment if it’s the latter, it’s fairly common in the frontend world to model that way. But it’s certainly not my preference.
fair point haha and that's certainly where my personal bias plays a role. When I see something I don't understand I like to look for the reasoning behind it. Some google searching, talking to coworkers, and a bit more googling usually sends me down the right path to finding more information
Clearly hooks are not a bad idea given how quickly they've caught on in the react/front end community too. So while the majority certainly isn't always right, there's usually a common reasoning worth understanding
> When I see something I don't understand I like to look for the reasoning behind it.
Similar view here, to an extent. But... with many topics, a majority of what I find is fanboy posts that strawman "problems" with earlier tech/tools, gloss over problems with the new way, and fawn over "hello world" examples. It can take a lot of time to get deep reasons, that may have some impactful value on your own problem spaces, and... at some point, I have to draw the line on "investigating all the bandwagons".
But also, at some point, one of the reasons to adopt something is "everyone else is", and there's some value in using "the latest trend" (hiring, bug solving, new tools, etc). And that may be enough value, but rarely does anyone justify their decision with "everyone else is doing it" - they try to give deeper 'technical' reasons, when (whether there are good/strong reasons) sometimes they just aren't able to do a good job.
The conclusion I came to is that some people are willing to put up with hooks if it means that they don't need to learn how classes and prototypes work. Maybe I'm wrong, but that's how it looks to me. Not all reasons are good reasons.
This seems at least part of it. And you don’t even really have to learn those things to use stateful React class components, they’re very seldom deeply inherited or require any `this` besides lifecycle methods and their own implementation. Rendered components are not instances.
I know there’s an aversion to classes in a lot of the current React community but the Component design in React was really much easier to reason with.
> Hooks are the place to segment out effectful code from otherwise pure code
I know what hooks are. Neither of those things is true. The hooks are called/instantiated in the same function body that returns the render. This performs some kind of stateful magic which makes that function itself inherently impure. Then that function returns a render which calls into that magic to perform further side effects, so that’s not pure either.
> The existence of a hook is what should alert you to the fact that there may be some side effect thing happening here.
That is true, and is for sure a benefit. But a class sends the same signal, but with the benefit of actually being fairly straightforward to reason about (notwithstanding the complexity of lifecycle, but lifecycle can be complex with hooks too).
> Not sure if this applies to you but I've found that most people who criticize hooks haven't made the leap to using custom hooks and have instead only used ones provided to them (useState, useEffect, etc).
I try not to use them at all, to minimize state and side effects as much as possible, and to encapsulate stateful code from rendering code.
I find them hard to reason about, whether in the core interface or as abstractions around the core. They fundamentally change the behavior of a function component from “this is a function which receives input (props) and returns a rendered data structure (JSX)” to “this sets up some initial state and returns a reference type of some kind that’s used to track that state over time”, but with the same syntax and semantics. The former is what attracted me to React and JSX, the latter is what I wanted to get away from. But making the same semantics do both is even worse than just having different semantics for both.
> Hooks are functions
Well not really, they’re routines expressed as functions, but they cause side effects on their enclosing context. That’s why, for example, you can’t conditionally call a hook. If you do, React won’t know to treat this component differently from components which don’t call hooks.
I love using hooks because you can write extremely terse code, but the magical rules are almost not even worth it. Hooks are not robust to reactive code patterns. There's no reason why hooks couldn't have been implemented as a dynamic graph that forgets/learns which render calls have been subscribed to the current computation graph. Overall, it's a poor design, but it is very expressive if you know what you are doing. For example, a typical reactive function would be able to handle conditional reactive dependencies:
When getReactiveValue() returns something <= 5, the reactive computation should automatically unsubscribe from getOtherReactiveValue, since that value changing no longer as any implications on the result of the reactive computation. Once getReactiveValue() changes to something > 5, the computation re-subscribes to getOtherReactiveValue. It's not hard to implement this if you're modeling your computation graph correctly. Unfortunately, hooks was implemented to depend on call-order, which makes absolutely no sense. This leads to actually more verbose constructs such as:
useEffect(() => {
if (iWantToRunThis(){
...
};
}, [])
This is better expressed as:
if (iWantToRunThis()) useEffect(() => {...}, []);
But alas, this is prohibited by hooks because of how they were implemented.
I've seen a lot of people using this strategy -- creating a null component that just runs an effect. It works and is totally valid, but IMO increases the API surface area unnecessarily. It should work without resorting to this "hack".
I've spent a lot of time in higher-order component and render prop hell — so many layers of indirection. The declarative behavior that HOCs and render props enable is one of the best parts of React, used in libraries like react-motion. But man, debugging layers of render props makes me itch.
Hooks have been a savior for me. I agree that there are a lot of sharp edges with hooks, but they're not magic. They're powerful and they compose really well. If you're doing anything behaviorally complicated on a React app, it's easy to build whatever abstraction is appropriate to your project.
One other common use for useRef that isn't mentioned here is to bridge your React component with a JS library outside the React ecosystem.
For example, if you wanted to instantiate a charting library like uPlot (https://github.com/leeoniya/uPlot) you might instantiate a uPlot object storing state and methods for the chart. You can perform the chart initialization in a useEffect hook, and use a ref to keep a reference to the instance you've created.
Very important use case. Actually, I am baffled how a lot of the common frameworks (React, Angular) omit some kind of documentation on how to integrate with third-party libraries that operate on the DOM directly (charting being a very good example) or how do something imperatively with the DOM (e.g. scroll to element after the user did something). In React I actually know how to do it properly, in Angular, however, I am completely lost how to do it.
Indeed, no hooks mentioned on that page, though. Also, on first glance, that looks like trivial integrations.
Ah, I also forgot to mention, what I'd like to see is more documentation on how to hide an imperative API behind a more declarative React/Angular etc. API. That's not always trivial but often what you want.
I think it's especially important because while you often can find ready-made wrapper for popular pure JS libraries they are usually abandoned (and thus don't keep up with the latest versions) or have some flaw that bites you sooner or later. My current recommendation is to write your own wrapper unless the wrapper is an official one of the authors of the underlying JS library. Naturally, that's not always feasible for complex libraries, but matter of fact you don't always need the full functionality of that library so you only have to wrap what you need.
I completely agree -- I've been bitten a few times using React wrappers that turn out to have subtle flaws or are unmaintained. Generally I just write my own wrapper with some clunky combination of hooks to manage the lifecycle of data.
One common pattern I end up using for a lot of imperative calls is a "useAsyncFunction" hook which runs any async function, returns state for loading, error, and the result of that function.
Unfortunately each library handles DOM manipulation, configuration options, callbacks, instantiation, etc. in its own way, and so I don't think there's any generalized solution.
> However, if you only reference inputRef inside useEffect then it'll always reference the DOM node so you don't need to worry about it being undefined.
This isn't correct; it's well documented [1], but people seem to often think this is true; if you reference it in multiple places or not is irrelevant.
Passing a ref in to the dependencies of `useEffect` doesn't work either.
Mostly I like hooks, but this one in particular seems to trip people up a lot; I'd go as far as to say that using callback refs should be the only supported way of using them; the normal one is pretty much broken by default for any component that isn't entirely trivial.
I'm a bit torn on the hooks feature since it seems like a great example of something that's easy but not simple [1], as well as going against React's prior principle of minimizing the API [2].
I'm glad the article also shows how to use the more traditional API as well, even if it is a bit more verbose.
I disagree that hooks increase the API surface. They decrease the React API.
Before hooks, if you wanted a stateful component you had to use class components with all of their specific methods (didMount, didUpdate, shouldUpdate, render etc).
Hooks allow functional components to have state, and a functional component in and of itself has an even smaller API than the class. The React API vastly decreases. A single hook, useEffect, replaces at least 3 methods (didMount, didUpdate, willUnmount) that previously had to written out separately in class components. Each of those methods had to contain logic for ALL your state and side effects so they could very easily grow in scope and be responsible for handling many things.
Now each individual concern can be packaged up in its own useEffect call. If I have state that is relevant to the useEffect call, than I just build my own custom hook that binds the stateful data with the useEffect.
Because hooks are just functions built up from other hooks, I'm no longer constrained by the React API (its effectively gone) and I can so much more easily the abstractions I need that can be shared across components.
There's lot of ranting here against react hooks. Just came here to say that I use them with much pleasure, they help me building functional (view) components which map their internal state or external state (pulled in via custom hooks or 3rd party hooks such as useQuery() from Apollo GraphQL). And a view's side effects are made explicit with useEffect(). Awesome, love it.
For some reason I never ran into any problems with lifecycle methods of class components.
I mean, for testing, having too many class methods is clearly annoying so I try encapsulate as much functionality into small and separate functions.
But yeah, apart from that I never saw the need to use hooks. I mean either I write a functional component without state or I write a class component. That's it.
I hope they don't deprecate lifecycle methods. Such a great interface!
wtf is wrong with yall hooks hater. i mean hooks are a pleasure to work with, surely better of the old class based implementation that is nothing but a class with some objects used as a components state. with all the previous lifecycle methods that causes unnecessary side effects. You said "What a bunch of confusing complexity...", dude if you dont get it it does not means that they are confusing. cm'on
Totally agree, I think most of the people hating on hooks haven't actually use them to write their own custom hooks or leverage their composable and reusable nature
Mixing stateful effectful code inside what would otherwise be a pure declarative render function leads to so much complexity in an attempt to bridge the two paradigms. Junior engineers are also constantly tripped up by the subtleties of `useEffect` and `useState`.
Furthermore I think attempting to "encapsulate" side effects is a bad approach to writing testable programs. Most React components that use hooks end up having several chains of promises inside them but no way to await the final promise meaning tests have to be full of
God forbid someone comes along and tries to be clever and replaces it with which now makes the test non-deterministic.Cycle.js is the only framework that seems to get this right by acknowledging that a component doesn't just output JSX, but actually outputs JSX, HTTP Requests, etc.
Look, I get that Redux was a pain in the ass with all the boilerplate, but how did we throw the baby (unidirectional data flow) out with the bathwater. I no longer advertise that I know frontend development because the entire react community seems to have lost its mind.