41 ms·
Hooks Considered Harmful
- quxpar 5y agoI want people to be critical of the method du jour, but these 'production-like code examples' defy most of the modern conventions that a React dev would use. For example the fetch example seems to ignore its own prior destructuring assignment to make an awkward reference to an object property.
- masylum 5y agoI agree, but the problem is that when you refer an object inside a hook, the eslint exhaustive deps will tell you to add that object as a dependency. In that example, it's easily solved with destructuring `const teamId = params`, but a lot of developers will blindly follow the eslint complaint.
- clansimus 5y agoIn that example, eslint will tell you to add `params.teamId` as a dependency, not `params`
- leroman 5y agoMaybe the comperator should be pluggable so we could opt for a deep equals, or what I'm thinking along the lines of- (for better performance) Object.is(..) || deepEquals (...)
- deleted 5y ago[deleted]
- PaulHoule 5y agoNice to see hooks get some tough love. Too many people think ‘functional programming’ is “not OO” but there is also that bit about “no side effects” and hooks are all about side effects.
- deleted 5y ago[deleted]
- nightski 5y agoThe entire "no side effect" aspect of functional programming is just a huge misunderstanding at best. It's unfortunate so many pushed that narrative. Many FP languages do not even restrict side effects. But those who do, like Haskell, do so in order to communicate where those side effects are taking place. In Haskell for example you can put all of your code in the IO monad and just have side effects anywhere. This works fine. But you quickly realize that there are benefits to separating out code with side effects from code without. The types make this clear. Haskell provides powerful mechanisms to weave functions that both have side effects and those that don't with ease while maintaining that clear separation. If anything FP in this manner is an extremely powerful version of side effects. It's not about "no side effects at all" but rather taking control of them and using them to our advantage.
- horsawlarway 5y ago> But you quickly realize that there are benefits to separating out code with side effects from code without. This belies your whole previous argument... Everyone understands that side effects are a requirement (literally - a program with no side effects is useless). Functional programming herds the programmer into a situation where code that creates side effects is consolidated into just a few places, and the majority of the code is pure functions. That paradigm has a real cost - consolidating side-effects isn't particularly easy, and you have to work to do it. But in exchange you get a LOT of pure functions that are 1. Easy to reason about 2. Easy to compose (because they have no side effects) 3. Easy to test Hooks are the antithesis of this - they create code them seems pure, and has the guile of being composable & testable, but in reality they are very hard to reason about. They have completely undone the work of consolidating state and side-effects into one location. It is very easy to call a function with a hook in it in a way that breaks that function, and it's usually hard to reason about what subtle differences are causing this new breakage.
- 5y ago
- jitl 5y agoThis really hit home for me: > I do not exaggerate when I claim that I find a dozen of hooks-related problems every single week while reviewing code. I also see sooo many issues when reviewing code like leaked event listeners, unstable props causing re-renders, etc. And these issues show up from teammates who otherwise write impeccable and trustworthy PRs in other regards. I enjoy writing hooks style code, and for me reasoning about lexical scope & closures is second nature. But for many engineers used to OOP, hooks code is the first time they’re asked to do this kind of reasoning since leaving university. In OO Java/JavaScript, it’s very normal to declare a new class and have the only two scopes be the current function’s locals, and class instance members. Hooks code on the other hand can easily reach two or three layers of local closure scopes, each with different lifetimes. I think this is fun and clever, but I also prefer to maintain boring and simple code… I’m worried that hooks ramps up trivial complexity too much in exchange for often-unneeded power. On the other hand, function components and hooks tend to guide people more toward splitting up their big mega components into smaller parts. At all companies I’ve worked for, product eng across the stack tends to produce mega objects cause it’s easy to just keep adding private methods to do one more thing, and splitting up responsibility of state encapsulation takes some extra reasoning. At least with FC/hooks, the API and linter force you to unroll funky logic or loops into sub-components, since you can’t call hook in loop or conditional.
- thr0wawayf00 5y ago> I think this is fun and clever, but I also prefer to maintain boring and simple code… I’m worried that hooks ramps up trivial complexity too much in exchange for often-unneeded power. I've been in and out of the React world for the last 5 years, and this statement hit hard for me. Between the major shifts in best practices, the abstractions on top abstractions and constantly tripping up on the slight syntax differences between JSX and markup, so many commercial React apps I've worked on make me want to pull my hair out, and not just because of hooks. In hindsight, the virtual DOM hype has not lived up to expectations, and I find newer frameworks like Svelte to be so much easier to work with. With the amount of React code running today, it's hard to see a future without it, but I'm so ready for it to be supplanted by something simpler.
- Cthulhu_ 5y agoAlright so, reading through all this I think I can summarize it: - Hooks are tricky because you need to pass them an array of dependencies, which is manual housekeeping - You shouldn't pass anything but primitives to a hook's dependency array if at all possible What is the alternative? Just pay attention to the two above, or go back to class based components? Or will there be a React-flavored JS/TS (like JSX / TSX) that has different closure mechanics?
- jitl 5y agoJetpack Compose solved this problem in two ways: 1. State you read in a ~hook~ aka Composable is/should be an instance of a special kind of observable object. You can create your own subclasses or just use the standard state container much like useState. This means the system has more information to produce minimum subscriptions/reactions at runtime. 2. The system used crazy compiler transforms to turn functions marked @Composable into reactive scoped hooks/components. Using the compiler eliminates a lot of error-prone boilerplate and bookkeeping code otherwise required for these kinds of systems in standard OO languages without monad+do-notation by adding a sublanguage “manually”. Downside to the Compose model is that it’s even more mindbending to understand. Developers are encouraged to surrender to the magic. I’ve yet to read/write enough Compose code to understand the cost benefit analysis yet.
- travisgriggs 5y agoIs SwiftUI pretty much the same in this regard? I’ve only taken time to do the trivial examples in either Compose or SwiftUI, but they feel very similar, so I’m wondering if your prognosis also applies to SwiftUI.
- deleted 5y ago[deleted]
- jitl 5y agoI think the Swift compiler does less magic for SwiftUI. They added a new literal syntax for the UI view tree literals, but other than that I believe the behavior is in the runtime. SwiftUI expects you to use Combine, Apple’s (F)RP system, to a greater extent than Compose expects you to use RxJava/Kotlin Flow. My impression is that Compose is more React-y and is actually an escape from RxJava FRP-land; I can translate most hooks to Compose after reading the Compose docs a few times but find it much harder to do the same with SwiftUI/Combine. Personally I prefer Compose because it’s open-source (so you can figure out how it works if you need to), has much better docs, and seems less welded to FRP stuff which I don’t enjoy. I don’t really have enough experience to really review either though.
- jchook 5y agoKind of neat how SolidJS solved these issues: each component function and hook runs only once. There is no need to worry about dependencies since it won’t need to run again! Hooks like useState() do not emit a value — they emit a “signal”, a function that returns the current value. This article introduced me to the concept: https://typeofnan.dev/solid-js-feels-like-what-i-always-wanted-react-to-be/ https://typeofnan.dev/solid-js-feels-like-what-i-always-want... Unfortunately there’s no react-native equivalent and the ecosystem is much smaller, but I have to imagine the React team has their eye on this alternative strategy.
- gr__or 5y agoI don't think this is quite right. The difference between observer approaches like SolidJS or MobX (and I'd also put Svelte in this box) and React's data-flow centric one is one of explicitness. With the observer approach change tracking is more implicit, i.e. embedded into the values you are using and the functions that are using it. Which does fix the problem of forgetting to declare dependencies, because using == declaring. Now what it does not guard you against *per se* is unnecessary re-runs, I am willing to bet there are tricky cases with tracking of nested objects and updates based on partial changes there. SolidJS does expose various tools for untracking and batching signals, so it might be a matter of trading initial explicitness/complexity for adding it later. This might be the right trade-off and untracking might be the smaller problem. But that feels like a somewhat team and product specific question. I think it is clear that without state libs React is to barebone to handle most somewhat-complex interactive apps, and a lot of them are observer based (MobX, Recoil, ...). But I do not see it as a silver bullet just yet.
- eyelidlessness 5y ago> SolidJS does expose various tools for untracking and batching signals, so it might be a matter of trading initial explicitness/complexity for adding it later. This is correct, but it’s exposed specifically for cases where you know more than the compiler does. This set of cases will almost always be smaller than similar cases in React, because ultimately Solid components are just functions. They don’t have a lifecycle, components themselves never rerun unless you call them. Those cases for React: you have to tell it when it should rerun or not rerun your components, your logic etc. Basically everything on the event loop that isn’t already participating in its reconciliation algorithm, and everything which has its own diffing logic. Those cases for Solid: likely interacting with other libraries with implicit lifecycles. Your event handlers will all run exactly as defined. You’re already invoking the signals and other logic which reconciles Solid’s state model. You just need to really mean it when you “get”, and libraries which aren’t aware of that need a little nudge.
- purplerabbit 5y agoYou can get around this identity problem by creating all derived/composed objects via `useMemo`. This ensures that their identity only changes when that of their dependencies do. This lets you get around this "identity problem", but comes with some issues: - Relying on `useMemo` preserved object identity assumes a semantic guarantee, which React docs tell us explicitly not to do [1]. Not providing this guarantee is ridiculous. If their cache is implemented correctly, this should be no problem. - The alternative is to leverage an external lib, which does provide this guarantee [2]. However, it's weird that bringing in an external lib as the more "correct" solution to this incredibly common problem (this is seriously relevant to like 1/2 the components I write) - Wrapping every bit of derived state in a `useMemo` hook is incredibly verbose and annoying, especially when you take dependency arrays into account. I feel like I'm writing generated code. 1. https://reactjs.org/docs/hooks-reference.html#usememo https://reactjs.org/docs/hooks-reference.html#usememo 2. https://github.com/alexreardon/use-memo-one https://github.com/alexreardon/use-memo-one
- srcreigh 5y agoWhy do you need identity except for increasing performance?
- purplerabbit 5y agoIdentity is necessary if you want to predictably trigger `useEffect` callbacks. Example: if you're not using a smart intermediate layer like `react-query`, you can unintentionally trigger loading states and re-fetches if you're not closely watching dependency array identities
- WorldMaker 5y agoThough this is also a strong reminder that though useEffect can entirely replace service layers and state management layers such as react-query/Redux/Mobx/Relay/what-have-you doesn't mean it necessarily should. (Ultimately that's the bottom line summary from this article.) useEffect is a very "raw" low-level tool, at some point it is a good idea to consider a higher-level tool (maybe one based on useEffect under the hood). Don't forget too that trying to do everything in raw useEffect code may be a sign of putting too much business logic in your views and abstracting that out can be a good separation of concerns no matter how you decide to abstract that service layer (and/or which tools like react-query/Redux/Mobx/etc you choose to make that easier).
- stevebmark 5y agoMaybe this author doesn’t understand the history of the term “considered harmful”? The points are valid, and there’s lots else to be wary of in hooks, but the title shows a lack of experience.
- pictur 5y agoThe main problem with hooks is that it takes a lot of somersaults to do simple things. Just like class components, there are things at the core that are not kept simple. which makes hooks very complex.
- kaycebasques 5y agoI am building my first non-trivial Next.js app now. It definitely took a couple days to get a simple "fetch data from a third party and render" use case working. And even now I'm not sure if I'm holding it right.
- jokethrowaway 5y agoThat's literally defining getServerSideProps to fetch data server side or just using useEffect if you want to fetch data client side. I don't recommend react in general but it should be pretty straightforward even with little react experience.
- izzygonzalez 5y agoI had a Next.js project due for an interview and it basically took my entire weekend, even with a working prototype in another framework. The interviewer later on told me that the assignment was his way of experimenting to find out if the company should switch stacks. I told him it was the worst experience I've ever had coding.
- jokethrowaway 5y agoI find it hard to believe as I had the opposite experience Next has amazing docs and sane defaults: it's the only way I would touch react.
- Toine 5y agoIt took you a few days to learn a new big & powerful React framework, and your experience apparently was bad, I'm ok with that. But, sorry : what does that have anything to do with hooks ?
- kaycebasques 5y agoHooks is the key architectural concept for implementing the use case I described! The problems that this comment described are exactly where I shot myself in the foot with hooks: https://news.ycombinator.com/item?id=30754873 https://news.ycombinator.com/item?id=30754873
- steinuil 5y agoThe main problem is that the hooks provided by React by default are low-level primitives of an incremental computing DSL, and you need to have a very good understanding of both JavaScript and the semantics of this DSL to work with those primitives correctly; it feels like having to reimplement strcat every time-- with every pitfall that you might fall into while reimplementing strcat in C. Maybe this could be solved by a hooks "standard library" that provides generally useful hooks like useTimeout and a useMemo that is actually stable as mentioned in @purplerabbit's comment.
- deleted 5y ago[deleted]
- thrwy_ywrht 5y agoBugs caused by faulty equivalency-related code have been common for a long, long time in React development. I couldn't guess how many bugs related to object destructuring I wrote, and fixed, long before hooks existed (although I can confidently say the former number will be significantly higher than the latter). I used to see these all the time on large Redux/React codebases. `mapStateToProps` was always a fun place to find them.
- lloydatkinson 5y agoI agree with this article, when I’m doing frontend development I much prefer to use Vue - with its options API (I ignore the composition api as it’s essentially a clone of reacts hook system). But when I do any react such as assisting another team using it, I am constantly surprised and reminded just how much of a bad developer experience react hooks are. The coupling of needing to constantly be aware of the intricacies of rendering while also balancing reactivity and data binding leads to probably the most offensive API I’ve ever had to use. When writing useState or useEffect etc the not always required last argument is a damn array or empty array or sometimes an array with several items. What does not passing an array mean? Well, I’m sure someone could explain at great length the complexity and alleged need for this but anyway: passing nothing as the last argument of useEffect results in an infinite loop. Passing an empty array means “don’t render again until actually needed”. The third option of passing a variable length array results in something else entirely I still don’t understand. Every article does it slightly different. Ultimately what this leads to for me at least is a developer hostile API full of seemingly intentional foot guns. The other day I had the situation where useEffect to call an API resulted in calling the API endlessly and constantly. Then I thought “ah I have seen this before, it means I have to pass an empty array”. It’s absolutely infuriating and smells of a bad API design. What happened to the importance of DX (developer experience)? As I said I’m sure someone could hand wave away the alleged need for this complexity but honestly it simply feels like a bad API. No one will ever convince me that an empty array vs no argument is “functional” because functional code values making side effects and purity deliberate decisions with a focus on clarity. How do other frameworks let you fetch from an API? Generally a simple assignment and await statement.
- bern4444 5y agoNo array means execute the hook on every rerender of the component. Empty array means execute the hook once on initial render. Non empty array means execute the hook when the value of anything in the dependency array changes. These are pretty simple to remember imo. An alternative design could have been to pass in an object as a second parameter instead of an array with these options details like { runOnlyOnInitalRender: true } Or { alwaysExecuteOnRerender: true } Etc. But that feels far more verbose than the three options we have now.
- ncfausti 5y agoI agree. Hooks are an awful hack / offer poor user (developer) experience imho. Whenever you need to have warnings and rules for using things (that require linter verification to make sure developers aren't shooting themselves in the foot with common/regular usage) it's an anti-pattern. They are far too easy to mess up, especially for something that is meant to be a fundamental part of the library.
- daok 5y agoI've been using SolidJS for some personal project and they use most of React concept but avoid pitfalls by running once the code of each component. Also, no need to explicit dependencies! The framework figures it out automatically. Really nice dev experience.
- dham 5y agoIt's weird after using Mobx since 2017, that a framework seems revolutionary because it can figure out dependencies. React has really brainwashed us.
- bitwize 5y agoNo, "state" doesn't mean "keeping things around while I compute other things". That's just intermediate results. State is defined by its ability to change as the computation runs; and the changes can affect how the computation unfolds. That's why it's "evil": uncontrolled state changes are difficult to reason about and are great cover for bugs.
- martimarkov 5y agoI know this might be a bit off topic but I wanted to mention it: FactorialHR had an forever free plan[1] which they killed and forced me to go onto a paid plan. This happened after I recommended them to a lot of founders which then backfired on me (albeit a bit - ppl still trust my opinion). Needless to say all those startups migrated to a different HR provider cuz they just couldn’t trust a company like that with sensitive data. But it’s just the way they did it and how after that there was a complete ghosting on my requests and queries. 1 - https://web.archive.org/web/20190425210338/https://factorialhr.com/pricing-plans https://web.archive.org/web/20190425210338/https://factorial...
- TameAntelope 5y ago> Most of those issues never manifest to the end-user, but incorrect code that is not a bug today will, eventually. No... what? Could not disagree more with this sentiment; this is the "for the love of the game" kind of stuff that completely loses focus on why we write code in the first place; to make money. Very, very few people have the resources to care about this level of problem, and far more often people who don't have the resources end up spending them on useless "improvements" like what's discussed in the article, rather than building things that users can use (or that make it easier to build things users can use). Hooks are surely flawed, in the same sense that literally everything in software is flawed. The point is not to select a way of writing code that doesn't have flaws; the point is to select a way of writing code that has flaws you can live with. Hooks have flaws, but hooks also have benefits that make writing software meaningfully easier. Losing sight of that is a great way to write an article that complains about problems that never impact the user's experience. It's just such a big red flag when someone talks about "incorrect code" that doesn't impact the user in any way. Huge, gigantic waving red flag.
- masylum 5y agoMaybe I expressed it incorrectly. When the code is incorrect, it fails in the future. So the user will notice it. This happens when your code relies on some context to work (for instance, if it relies on how the data structure was allocated somewhere up the component hierarchy). When the context changes, then that piece of code starts failing, therefore impacting users.
- TameAntelope 5y agoBut now it just sounds like you're saying, "When the code changes, the code changes." which feels obvious? Of course when you change some of the code without changing other parts that rely on the original code, it will break. I'm not sure why that's unique to hooks.
- inglor 5y agoI work in an infra team in a large codebase for Microsoft. We are the people who do the sort of bug-analysis to figure out what technologies work well and what needs improvements. Hooks are a constant area of struggle and people make tons of mistakes (forgetting to cleanup a useEffect, useState closures, and needless useCallback are the top 3) with it. I dare say that if we didn't have MobX things would have been much much worse. The annoying bit is that other frameworks like Vue or Solid have MobX like Reactivity baked in which makes things much much simpler.
- jitl 5y agoHow does Mobx help your code work better? Does it specifically salve hooks wounds, or just because you don’t need to manually manage subscriptions in a component? I’ve looked at Mobx and am concerned about mutation. I would like a magically reactive state container that always returns immutable views of the state for local usage. Maybe I should stop worrying and switch everything to Mobx.
- cloverich 5y agoBecause you don't need to manage subscriptions, there's a whole class of hooks you don't need to write in the first place. Then on top of that, you get extremely good performance. Having to rarely think about hooks and basically never think about performance, for me, free's up tons of cognitive space to focus on the actual implementation of the UI. Its not a perfect system, but I haven't found another I'd rather use.
- brundolf 5y ago+1 for MobX, can't recommend it enough [also a quick shill for the general-purpose language I'm working on which internalizes that mechanism at the language level :)] https://www.brandons.me/blog/the-bagel-language https://www.brandons.me/blog/the-bagel-language
- purplerabbit 5y agoI only wish Mobx's API was terser and more opinionated. Hard to derive a good Mobx pattern from their bloated API (>100 exports) I've created a wrapper library which is basically a more-opinionated, higher-level version of Mobx if anyone is interested: https://www.npmjs.com/package/r2v https://www.npmjs.com/package/r2v
- kubb 5y agoconsidered by you. for me they're the best way to write react components, and i was never confused as to how they work. also the post is laser focused on effect hooks which are arguably the most difficult to deal with because of their intended application (reflecting state updates outside of the virtual DOM)
- ezekg 5y agoI'm not a fan of hooks, really -- it feels like a big black box. I tried to find the source code, but I wasn't able to grok how it all actually worked. Maybe just me, but hooks are a bit too magic-ey for my tastes.
- brimble 5y agoI read it when they first announced it. There was a lot of indirection, which is probably why you had trouble, but in the end, hooks were registered to a list that was associated to your component object (yes, object) in the big, central state machine that is React. It may have changed since then, but hooks were basically just weird shitty methods & properties with silly non-standard declaration syntax and totally bizarre access/invocation rules, in a language that already had fairly normal objects & methods and all that.
- postalrat 5y agoBecause reusing code in those class based components was never as easy as creating a new hook.
- dkarl 5y agoFor this reason I was skeptical of them from the start. It's a broken formula: X is challenging for some developers to understand, so we'll replace it with Y which practically nobody will understand, but it's okay because they won't need to. In order to work, the formula has to follow Dijkstra's admonition about abstraction: "The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise." The implementation of Y can be opaque to users, but it has to be precisely defined on the level that users think about it. Hooks were not presented this way. There was never a precise definition of hooks presented to users, at least not one that I could find in a succinct form in the documentation. To me, the documentation amounted to a handful of examples and a couple of rules to follow, and you were supposed to pattern-match your way to success, without any precise definition to fall back on when you were uncertain.
- 5y ago
- dgb23 5y agouseEffect is a bit tricky but powerful. The issues mentioned in the article are things you can avoid by reading the official docs a bit carefully and playing around for a few minutes. The other issues mentioned are JavaScript specific (not React/hooks specific), currently there are libraries that can help (or using a compile to JS language that has proper equality semantics). In the future there might be an implementation of the proposed immutable "Record" and "Tuple" types that will have data literal syntax and all the properties that one wants when writing FRP style UI code.
- memco 5y agoIn the final example there is a create effect and an unsafe create effect: could these be collapsed such that something checks for any argument that makes the call unsafe and defer to the unsafe version but otherwise chooses the safe version? Seems the split still relies on the human to know which one they need to use without that extra bit of logic.
- draw_down 5y ago
- simonlc 5y agoNews: People don't learn the tools so mistakes are made Author: Because I see mistakes, these tools are bad I consider your teammates harmful. All this stuff is mentioned in the excellent React documentation.
- skydhash 5y agoI'd said that it is a non-issue. One of the main tenets in React is that your component re-renders when the prop changes. I think anyone should research what exactly change in this context means and take care of not triggering the re-render when it's not wanted. The author goes out of the way to use examples that are what I would consider bad code and not something that should ever pass a code review. I have not dealt with many junior devs, but creating an object either by using a literal or restructuring is still creating a new object and no one should expect it to be the same. Equal perhaps, but not the same. Maybe someone should explain scope and instance lifetime in the JavaScript world instead of blaming React for these. Because there is no surprise that the component re-render when you change the prop.
- clansimus 5y agoYea, the examples seem disingenuous to me. > Most bugs can be solved by moving hooks away from the components and using primitives as the only dependencies. Or just use primitives in the dependency array of your existing hooks?
- randall 5y agoExactly.
- masylum 5y agoThe point is that "a prop changed" can't be reasoned within a component. If I receive an object as a prop, I can only reason if that pointer in memory will be the same, not wether it has changed. The allocation can happen up in the component hierarchy making this a subtle for even the most senior developers.
- koprulusector 5y agofacepalm > The mechanisms to retain memory have a lot in common. Classes use this, which refers to the object’s instance, while functions implement closures - the ability to remember all the variables within their scope. Non-arrow functions, like the example, defined with the `function` keyword also have `this` context. If you want to limit `this` and rely solely on closures, you probably want an arrow function.
- mejutoco 5y agoIMO hooks are a DSL implemented in js. React went from class to function components to hooks as preferred practices. In the process, the previous was not cleaned up (until very recently, maybe still, you needed a class component to catch exceptions in a component). It feels like change for changes sake, were only new features are explored without making the whole consistent. I like React a lot. A simple conceptual model, like elm or other frameworks mentioned here, would work better that this constant change.
- dragonwriter 5y ago> React went from class to function components to hooks as preferred practices Hooks work with function components, and are how component state and the equivalent of lifecycle methods are implemented in function components. For a while with function components, higher-order components were a common complementary approach to hooks, which have themselves largely had their function subsumed by hooks, so you could maybe describe the trend as: class components => function components + hooks + HOC => function components + hooks.
- mejutoco 5y agoThat sounds right. In my own anecdotal experience I see a much bigger push to use hooks for everything, where before only useEffect and a few others would be expected. I wish old methods would be deprecated and all features made available in function components.
- kazinator 5y agoWe know from Lisp that, for instance, you can write a while loop like this. (defmacro while (condition &rest body) `(let ((cond-fun (lambda () ,condition)) (body-fun (lambda () ,@body)) (while-macro-run-time-function cond-fun body-fun))) Then we have a run-time support function: (defun while-macro-run-time-function (cond-function body-function) (loop while (funcall cond-function) do (funcall body-function))) Closures allow macros to parcel off expressions or bodies of expressions into functions, so that control structures can then be made "remote": put into a function. This has the benefit of keeping expansions small. Another benefit is that since the core logic is in the run-time function(s), those can be updated to fix something without having to recompile the macro invocations. Somehow, the sky doesn't fall in Lisp land; we don't need articles like, OMG I learned about this in 2018 and it's so dangerous.
- kazinator 5y agoI'm finding that programming language articles by JavaScript developers are a lot like Gang-of-Four Design Patterns in the following way. When you translate them to Lisp, they either disappear entirely or greatly simplified.
- Igrom 5y agoI respect the opinion that JavaScript can be an unintuitive language to write in. The concurrent existence of abstract and strict equality operators, the former's lack of predictability, as well as their performing shallow comparison are all a fount of problems. While I can't vouch for the relevancy of the book nowadays, reading "JavaScript: The Good Parts" when I was starting out myself a) conferred a decent knowledge of the gotchas and b) helped me understand that paying the cost in decreased concision to fence off the dangerous parts of the language (e.g., the abstract "==" comparison operator) was very much worth it. Nowadays, with the abundance of linters and the existence of TypeScript, hopefully the better part of JavaScript programmers get to code in a safer, stricter subset of the language — nevertheless, as the author points out, one can trip anywhere. That being said, how do any of the above deficiencies constitute an unique indictment of React Hooks, and not, say, that of other UI frameworks or the language in general? React Hooks have introduced neither closures nor shallow comparison to the language. Most of the author's grievances are addressed fairly clearly in their comprehensive official documentation[0] (people still read manuals, right?) or quickly become self-evident through practical usage. Owing to the framework's popularity, linter support for hooks is also extensive, with one's code already being automatically verified against the majority of the documentation's commandments. There's probably not much more than a handful of classes of errors that a developer has to manually watch out for. I don't mean to say that there's nothing wrong with hooks, but a comparative review that pits them against other frameworks would have been more constructive. Lastly, the use of the "considered harmful" moniker in the title in spite of the relative scantness of constructive criticism in the article lies somewhere between clickbait, scaremongering and false expertise. It's to be considered harmful[1]. >Hooks benefit from closures because they can “see” and retain information from their scope, for instance, in the example above, user. However, with closure dependencies being implicit, it is impossible to know when to run the side-effect. The frequency at which an effect is to run is wholly orthogonal to whether the associated function accesses any variables in its environment; it is decided by the developer through setting the dependency array, which is passed as a separate argument to the useEffect function. No relation whatsoever. [0] e.g., https://reactjs.org/docs/hooks-reference.html#usecallback https://reactjs.org/docs/hooks-reference.html#usecallback [1] https://news.ycombinator.com/item?id=9744916 https://news.ycombinator.com/item?id=9744916
- deleted 5y ago[deleted]
- grayrest 5y agoI have not adopted hooks and have argued against it for others on the team. My primary objection is that the paradigm is not clean. Class based components are fairly straightforward. The entire render function is run on every change. Solid/Svelte are fairly straightforward. The component is run once and then only the reactive parts change. Hooks run the function on every change but there are islands of non-running code inside the constantly rendering body where only run when their dependencies change. This strikes me as an obviously intermediate solution and I don't want to spend time porting/developing something that's going to be obsoleted in a year or two. The next framework generation is underway and the time for early adopters to move on is in the next year or so. The main reason to hold off is that the handling of SSR/hydration/etc is in flux and I believe the primary benefit of the upcoming generation is going to be ability to avoid shipping most component code over the wire. I have a lot of respect/love for React. I've seen quite a bit of criticism that the vdom idea is inherently inefficient but the important thing about it was that it was reliable. Lots of JS used to do all sorts of crazy stuff with the dom underneath you but React has basically cleared the field of most of that which allows more fine grained approaches to work consistently.
- softfalcon 5y agoWe use a thing called Relay (https://relay.dev/ https://relay.dev/) to manage our querying, mutating, and state management. When you query data, it is cached locally in a shared data store that will automatically trigger re-renders of components that take in the data types as a dependency. You don’t have to write an update to the data, you query it and Relay updates all the underlying models in the local state cache. The data types are correlated to your components via GraphQL fragments which allows Relay to know what and when to update. Because most of the values you are using in your component are already stateful, you end up using A LOT less hooks since most state updates will trigger and happen for you. It’s incredibly powerful once you get going with it. The downside is getting it to work properly with the type generation. It can be rather finicky and Facebook likes to rewrite the API out from under you every couple years.
- didip 5y agoThis is just a casual observation from a back end/infrastructure guy perspective. Does building a rich GUI experience on a web application need to be this complicated? I still remember the days when rendering are mostly done on server side and Javascript was used as progressive enhancements. The web application back then were quite interactive and building them were somewhat simpler than the current state of the art.
- AgentME 5y agoReact is a great way to do progressive enhancement: you can have the same component code run on the server and client. On the server, it will only be used for creating static html. On the client, the component code will bind to the already generated HTML and allow client-side interactivity. Writing rich UIs with progressive enhancement without this benefit was over-complicated pain before React. All of your UI code would either be based around binding to static HTML generated by the server or be based on locally-generated elements; if you ever had a widget that had been generated by the server that you now want to generate client-side in some context, or vice-versa, then you had to have multiple separate code paths on the client for that in addition to the separate server-side code for the widget. Having the component's code defined once in a way that works for all three contexts (client-side generation, server-side generation, client-side binding to server-generated HTML) is great. I think people assume that because React is a newer way of doing things that it doesn't work well at the old goals (progressive enhancement) but the opposite is true!
- steve76 5y ago
- pandesal 5y agoYou don't need something like React for building rich GUI today but once you have a sufficiently large codebase and a large team of developers, you're going to end up building inferior in-house versions of libraries/frameworks like React/Redux just to have any sane structure in maintaining the application as well as support future feature development. The currents state of front-end is complicated but I absolutely prefer it over the chaos of old timey vanilla javascript.
- jakelazaroff 5y agouseEffect is a foot gun, agreed. But in the large React codebases I've worked with, my team has had basically zero issues with any of the other hooks.
- i_like_robots 5y agoI was a very early adopter of React and I am very grateful for it. It’s now it’s my job to listen carefully to the concerns of my teams and I’ve helped developers of all abilities and experiences through their troubles on all sorts projects over the years. In that time I’m sure I must have discussed all the good and bad things that can be said about React but if I’m honest I’m not hearing as much good nowadays. I think the part of the reason React has been so successful is because its rules are easily communicated; components which render HTML or composed other components, state lives inside those components and props are passed down between them. How to deconstruct an interface into individual pieces and how data should flow through them really resonated with a lot of people. But I think the shift to hooks means we’ve have lost the clear rules that made React so accessible to newcomers. Although hooks are still easy to get started with they seem to create confusion easily, and one wrong dependency or deriving state incorrectly and your laptop becomes a heater. This makes it more difficult for developers to focus on what they’re meant to be building because their heads are filled with an uncertain palette of distributed logic. Now that the tiny API and rapid learning curve seem to have been abandoned I’m starting to think React may no longer designed to help solve the problems me and my teams are being asked to solve and perhaps the reason why hooks aren’t clicking for many people isn’t because they aren’t smart or willing enough, it’s because the mental model required to use React effectively no longer overlaps enough with the things we’re usually building.
- ajkjk 5y agoNo way, hooks are great. All of these problems exist in Class components too -- especially accidentally rebuilding `params` in `mapStateToProps` or whatever. Figuring out how to do things on re-renders with componentDidMount and componentDidUpdate is a total disaster. Don't even get me started on getDerivedStateFromProps. God, hooks are better in every way.
- akagusu 5y agoI don't know why, but all this story made me think about C memory allocation and undefined behavior. Perhaps should I ditch hooks for Rust?
- andrewstuart 5y agoOK so criticisms heard. What is a solution?
- chrisco255 5y agoRead docs, implement linters. It doesn't take much time to learn the dependencies and nuances of useEffect (a single afternoon of reading the docs). Otherwise, you can always still use Redux/Rematch or class components (they're still there) or any other state management solution and just pass in props.
- thomasfromcdnjs 5y agoMy personal solution is to not just nest them or over complicate them too much. If they stop being expressive, I will just start using class components again. Aka a mixture of them in my code bases.
- raiflip 5y agoFP has also benefited from a hype cycle, and FP does improve code in a lot of ways. Making your data immutable makes it easier to reason about, and pure functions prevent surprises. However if your argument is state is bad, and FP avoids state, so that is why it is good, but you encounter a scenario in which state is required, then the benefits of FP start to degrade. The reality is closer that, state should be immutable, and minimized as much as possible, but at the end of the day, almost every interesting problem requires storing state. Once you reach that point, classes are simply a better solution for state than closures. Especially if your class and its variables are immutable, you get all the benefits I mentioned and none of these tradeoffs. Your state is explicitly stated.
- travisgriggs 5y ago+100 Sorry for not adding more. But just so this. I’ve loved learning Elixir of late. But my programs need state. I really miss how well I could model general state using objects like those afforded by Smalltalk and Python. I wish there was a best of both worlds, but I wonder if to do one well, you have to overreach so much with the one philosophy, that the other just can’t be tolerated well in its presence.
- UlisesAC4 5y agoBut agents and genservers are a good way of dealing with state.
- serverlessmania 5y agoWhat classes vs function have to do with OOP vs Functional??
- hughw 5y agoUsing ImmutableJS [1] for all the state variables avoids the identity issues he raises. [1] https://immutable-js.com https://immutable-js.com
- Tade0 5y ago> It was such an innovative API that it took the frontend world by storm. Only the React community actually. It's the majority, but I wouldn't equate it with the whole frontend community. I remember when they were introduced - it was during the brief moment(1 year) during which I worked in React. My code-smell-o-meter indicated that this is going to cause problems in the long run. Ultimately it did, since you can't just replace everything with hooks and call it a day, as that is likely to cause massive performance issues.
- ChrisMarshallNY 5y ago> The argument usually follows that state is evil, hence object-orientation must be avoided. I've found that it's really, really difficult to design good UI, without state. The "solution" that many UI systems use, is leaving the state in the view, but that often results in a pretty degraded user experience. Sometimes, the state needs to live in the model, as it may interact with a whole gaggle of views, or apply sets of state. So the "solution" there, is to tie the views together, or save the state in little "statelets," connected to views; resulting in an ... interesting ... design. I've come to learn that "hard and fast" rules are a mistake. It's been my experience, that I often need to approach a solution in a hybrid manner, and really appreciate new techniques and technologies. But sometimes, we need to stick with the classics.
- LAC-Tech 5y agoI think people need to wake up to the fact that react encourages bad architecture. You can tell me react is a big brained functional library all you want. Fact is you're putting business logic and mutable state inside your functions from props -> jsx. The fact that setState is a 'hook' doesn't change the fact you're setting state. Every react code base I've come across looks exactly like what they told us not to do in the WinForms and Java Swing days - code behind.
- serguzest 5y agoIn our codebase, we use useEffect without exhaustive-deps linting rule. I don't think it is bad in hands of developers who have basic understanding of closures in javascript. Otherwise, we have to resort to complicated logic to provide same functionality like keep tracking of changed variables. Are we wrong to do that?
- kietay 5y ago> It is then safe to say that the only difference between programming paradigms is how long you keep stuff around and the space-time tradeoffs that these decisions entail. That is absolutely not the only difference.
- lxe 5y ago> This decision forces the programmer to be responsible for making explicit those implicit dependencies, thereby functioning as a “human compiler” of some sort. Declaring dependencies is manual boilerplate work and error-prone, like C memory management. I think hooks solved some problems at the cost of introducing others, such as this. It's unclear to me still whether the value trade-off has been worth it.
- wishawa 5y agoI created a tiny library to make hooks out of classes a while ago. Just posted on Show HN after seeing this. https://news.ycombinator.com/item?id=30760696 https://news.ycombinator.com/item?id=30760696
- gitgud 5y agoThe proposed solution seems to gloss over many of the trade-off's that you're making. > Most bugs can be solved by moving hooks away from the components Well you can't modify state from outside the component, and the context becomes harder less clear... to me hooks are still the best option.
- drc500free 5y ago17 years ago, a colleague convinced the team to rewrite a Java server application using Aspect Oriented Programming. It was a new and magical technology that would allow us to separate the meaningful business logic from annoying logging and bookkeeping - at the slight cost of losing track of standard OO control flow and scoping. The 12 months of hell that followed cannot be described in a simple hn comment. The dangers of any technology that confuses code reviewers about scope is one of a small handful of lessons that have stuck in my brain, almost a decade after I left engineering behind.
- spoils19 5y agoThousands of developers every day are using React with hooks. I sincerely hope that they are also not facing "months of hell".
- eyelidlessness 5y agoJudging by some of their actual expressed feelings about using hooks, there is definitely a subset of React devs who are definitely experiencing months or more of hell. The React team is, admirably, actively working on a next version of docs to help address this. The fact that the docs project is hooks-first is both a testament to how radically React itself has changed over recent years, but also at least a smell that hooks themselves may be inherently a pain point. Speaking for myself, I’ve only used them a few times, but I’ve found footguns in even very simple usage. Not months of hell, but definitely propositionalToleranceForHell < actualHell. And sure maybe that’s lack of experience with hooks specifically, but they have nearly-totally-whitespace-and-punctuation-diff equivalents which are nowhere near as complex to actually use effectively.
- Flankk 5y agoHook are worse than lifecycle methods, they are difficult to understand, and they are not elegant. When you run into trouble, it is your mental model to blame. You are told to learn to think in hooks, which is to say, to do things in an unintuitive way. That is a heavy price to pay for composability.
- javcasas 5y agouseEffect(() => { console.log(`hello ${user.name}`) }, [user.name]) There OP, fixed that for you.
- dceddia 5y agoOn the object identity issue - I always wished JS had some way of getting at a unique (printable!) identifier for objects. It’s not very often but sometimes it would be so very useful to be able to console.log(obj.underlyingPointer) to see if an object’s identity is changing. It would also be useful from a teaching perspective.
- ncmncm 5y agoThis is a big part of what makes the IBM "MQ series" message passing system so bad: everything in it is implemented via hooks.