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.
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.
> 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."
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.
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. async () => {
await foo();
await bar();
}
() => foo().then(() => bar());
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.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.
(FWIW: I've been there myself)
I can see why some people prefer to use classes where the design is cleaner (even though you are more limited).
*: https://reactjs.org/docs/hooks-rules.html#:~:text=Only%20Cal....
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.
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 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.
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.I think this talk by Andre Staltz is the best explanation and solution of the problem https://www.youtube.com/watch?v=SXdtrhn8iII
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.
That will also help seniors when they are tired or under time-pressure.
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).
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.
I still vastly prefer JSX for frontend dev but I think really that’s about all I care for in this ecosystem.
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.
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.
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?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.
const myReactiveComputation = () => {
if (getReactiveValue() > 5){
console.log(getOtherReactiveValue());
}
}
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.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.
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.
https://reactjs.org/docs/integrating-with-other-libraries.ht...
I'm glad the article also shows how to use the more traditional API as well, even if it is a bit more verbose.
[1]: https://www.infoq.com/presentations/Simple-Made-Easy/ [2]: https://www.youtube.com/watch?v=4anAwXYqLG8
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.
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.
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.
[1] - I think https://medium.com/@teh_builder/ref-objects-inside-useeffect... covers this pretty carefully.
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!