[HN Gopher] Hooks Considered Harmful
___________________________________________________________________
Hooks Considered Harmful
Author : smtx
Score : 197 points
Date : 2022-03-21 12:11 UTC (10 hours ago)
(HTM) web link (labs.factorialhr.com)
(TXT) w3m dump (labs.factorialhr.com)
| [deleted]
| lloydatkinson wrote:
| I 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 wrote:
| No 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.
| lloydatkinson wrote:
| > These are pretty simple to remember imo.
|
| But why should we? No other reactive framework makes the
| developer go through this ritual. Even RxJS is clearer to
| understand.
|
| > Etc. But that feels far more verbose than the three options
| we have now.
|
| A better designed API would require neither.
| bpicolo wrote:
| The composition feels a lot closer to the "ratom" concept from
| the Clojurescript ecosystem (https://github.com/day8/re-
| frame/blob/master/docs/flow-mecha...) than it does hooks. I
| don't hate it at all.
| jitl wrote:
| This 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.
| bilalq wrote:
| > 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 think this is true. However, I don't think I saw less bugs
| with `component(Did|Will)(Mount|Update|ReceiveProps)`.
| Lifecycle events are intrinsically about state management, and
| that has always been the root cause of many bugs. This isn't a
| React specific problem either. Back in AngularJS 1.2.x days,
| the $scope and digest cycle was the source of many bugs. In
| Backbone, people's two-way binding between views and models
| were the source of many bugs.
|
| Are React hooks complex? Yes. I don't think they're worse than
| what existed before though.
| totaldex wrote:
| I don't entirely agree - the old React way was verbose and
| explicit about when events are executed. This allowed
| developers to more concretely reason about logical workflows
| in applications.
|
| With hooks, we traded verbosity for a single interface that
| does it all (assuming you know how to hook up your
| dependencies correctly, or compose helper hooks to manage
| state comparisons). Hooks allow you to do mostly anything
| lifecycle methods did, but they're a lot trickier to reason
| with, review, and develop.
|
| This all goes away if all your developers are functional
| maestros - in practice, it's lead to buggier code across our
| various frontends.
| bilalq wrote:
| My experience has been pretty different. With lifecycle
| methods, you had the implementation details of a single
| concern split across the constructor, multiple lifecycle
| handlers, and sometimes even the render function when refs
| were involved. Hooks can express full concerns in a
| reusable way. This is a valuable abstraction that
| previously required complex higher order components to do.
|
| People would also constantly get tripped up over
| `this.props` and `this.state` when it came to computed
| state values. Now a simple `useMemo` simplifies and
| expresses that intent way better than setting something
| conditionally in the constructor and doing a bunch of
| conditional checks on componentWillUpdate before calling
| this.setState again.
|
| Edit:
|
| Oh, and the improvements with Redux are life-changing. The
| `useTypedSelector` UX is so much better than writing a
| mapDispatchToProps, mapStateToProps, and then having a
| bunch of merging ceremony there.
| totaldex wrote:
| I'd chalk it up to a difference of opinion then - I like
| simplicity, but I'd take verbose clarity over it any day.
| Having to explain to newer engineers the nuances of hooks
| (and the permutations required to wrangle them in) is
| harder for me than saying "this exact lifecycle method is
| what you're looking for, take a look at its
| documentation".
| [deleted]
| thr0wawayf00 wrote:
| > 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.
| djbusby wrote:
| I had bad feelings about React when I first saw it. Then I
| saw RiotJS and that just clicked for me. I like Svelte too -
| haven't put it in production tho
| bpicolo wrote:
| I have to say, I've never felt this in the Vue ecosystem.
| It's relative simplicity in state management, and slower
| relative paradigm shifts have been pleasant. The 2->3 shift
| wasn't perfectly done (and it did split the state management
| stories), but overall I've felt like I "kept up" fairly
| easily, compared to React.
|
| And that's while writing a lot more React than I did Vue.
| jitl wrote:
| There's too much framework in Svelte for me; I don't like
| feeling so estranged from "regular" code. I think SolidJS is
| more appealing - although it might be _even more clever_ than
| React...
| thr0wawayf00 wrote:
| To each their own, I guess.
|
| I haven't built anything with SolidJS, but it's not my
| style based on the docs. It is interesting that you bemoan
| being so far from "regular" code in Svelte when it's
| basically a minimal superset of HTML, as opposed to Solid,
| which uses JSX (which feels very far from "regular code" to
| me). Also given the fact that Svelte is a compiler that
| emits vanilla JS, I have a lot of more control over
| performance, compile-time checks for things like a11y,
| unused deps, etc. I'd argue that svelte source code looks a
| whole lot more like it's output than SolidJS does, but
| that't just me.
|
| It's easily the best overall front end developer experience
| I've seen, but I've only built smaller projects with it so
| far.
| com2kid wrote:
| Svelte feels very minimal to me. It is regular JS where I
| can say "shove the value of this variable onto the web page
| and rerender that component when the value changes" but
| saying all of that is just $:
| danielvaughn wrote:
| Interesting - I began using Svelte after years of using
| React, and I found it to be far less framework-y. I feel
| much more connected to what's actually happening, but I
| also haven't built out a very complex application with it
| yet. Is it the weird $: reactive model that you don't like?
| That was the weirdest part for me, even if it's supposed to
| be "normal" javascript.
| tshaddox wrote:
| > I do not exaggerate when I claim that I find a dozen of
| hooks-related problems every single week while reviewing code.
|
| I'm curious if the author sees dozens of problems every week in
| code review due to, say, unexpected null/undefined values,
| mistyping variable or attribute names, using the wrong number
| of equal signs, failing to catch errors and handle rejected
| promises, etc.
| acidbaseextract wrote:
| I'll give you my answers to your questions as someone writing
| production JS/TS. Answering your questions is precisely
| illustrative of why hooks issues are hard to catch.
|
| _unexpected null /undefined values_
|
| This is an issue and has led to bugs. Switching to TS and
| making the compiler flag them fixed it.
|
| _mistyping variable or attribute names_
|
| Not an issue. Code obviously breaks if you have incorrect
| names.
|
| _using the wrong number of equal signs_
|
| This is an issue but causes few bugs. Linting generally
| catches it, though cute boolean punning still bites us.
|
| _failing to catch errors and handle rejected promises, etc._
|
| This is an issue and has led to bugs.
|
| Pretty much anything where there's some implicit details that
| the compiler or linters can't reason about programmers find a
| way to get wrong. One thing I like about the hooks linter
| setup is that what it encourages you to do by default will
| prevent most bugs, only lead to potential performance issues,
| unnecessary rerenders, unnecessary refetches.
| rileyphone wrote:
| > Pretty much anything where there's some implicit details
| that the compiler or linters can't reason about programmers
| find a way to get wrong.
|
| touching on an immortal truth!
| bilalq wrote:
| >> failing to catch errors and handle rejected promises,
| etc.
|
| > This is an issue and has led to bugs.
|
| > Pretty much anything where there's some implicit details
| that the compiler or linters can't reason about programmers
| find a way to get wrong. One thing I like about the hooks
| linter setup is that what it encourages you to do by
| default will prevent most bugs, only lead to potential
| performance issues, unnecessary rerenders, unnecessary
| refetches.
|
| This is something that can be mitigated via the no-
| floating-promises linter rule if you're using
| TypeScript[0]. For the cases where you actually want to
| just swallow errors, you can just add a `.catch(noop)`.
| This makes such situations explicit. You can get even
| stricter with the no-misued-promises rule[1].
|
| [0]: https://github.com/typescript-eslint/typescript-
| eslint/blob/...
|
| [1]: https://github.com/typescript-eslint/typescript-
| eslint/blob/...
| bocytron wrote:
| Can anyone here recommend a blog post that go through the most
| common mistakes when using hooks?
| WorldMaker wrote:
| The article here ("Hooks Considered Harmful") actually
| probably would be better called "Most Common Mistakes When
| Using Hooks". It seems to cover the most common ones I'm
| aware of.
| Jcampuzano2 wrote:
| Don't have a blog post, but the majority of issues you will
| find are with dependencies arrays for
| useMemo/useCallback/useEffect.
|
| Most common I see:
|
| 1: Missing dependencies (for folks who don't use the linting
| rule)
|
| 2: Not understanding reacts async state model: example being
| expecting state to have been updated immediately after
| calling setState inside an effect
|
| 3: Not understanding closures: an example is creating an
| interval in an effect that uses and updates a useState value,
| but being confused why it isn't updated when the interval
| repeatedly fires.
|
| I agree wholeheartedly with the author. Hooks are powerful
| and even I was super excited to start using them when they
| released. But now I think the hooks paradigm leads to even
| worse and bug-ridden code than what we had before.
|
| The docs are pretty good, but don't seem to cover very well
| that when using hooks you really need to know JS well, which
| includes equality in JS, closures, etc otherwise you will be
| guaranteed to shoot yourself in the foot.
|
| There is so much more business logic mixed in with component
| code at seemingly random in projects as well such that it
| makes finding where "stuff" happens even more difficult than
| before.
| ChrisMarshallNY wrote:
| _> 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.
| grayrest wrote:
| I 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 wrote:
| We use a thing called Relay (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.
| TameAntelope wrote:
| > 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 wrote:
| Maybe 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 wrote:
| But 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.
| LAC-Tech wrote:
| I 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.
| Cthulhu_ wrote:
| Alright 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?
| [deleted]
| jcelerier wrote:
| A sane solution for exactly this problem has existed for a
| decade now: https://www.qt.io/product/qt6/qml-
| book/ch04-qmlstart-qml-syn...
| spiffytech wrote:
| > What is the alternative? Just pay attention to the two above,
| or go back to class based components?
|
| React may have limited options with this design, but other
| frameworks have taken other approaches to the problem:
|
| Vue/Svelte/MobX only run the setup code for hooks (or closest
| equivalent) once. Derived values and effects are automatically
| run without specifying dependencies - the tools detect what an
| effect reads while it runs, and track dependencies for you.
| Since effects are only set up once, closure values from the
| setup scope don't expire/disappear, so they can't go stale in
| the same way as in React (caveat destructuring). I think Solid
| is in this camp too, but I haven't used it.
|
| Frameworks like Mithril and Forgo ditch state tracking and
| effects entirely. You explicitly tell the framework when to
| rerender etc., and everything else is just you calling
| functions and assigning variables without the framework's
| supervision.
|
| Crank.js extends the explicit-rerender idea by using generators
| for components. This preserves the "a component is just a
| function" feature from React, but avoids the hooks pitfalls by
| only executing a function once.
|
| Hyperapp doesn't have the notion of components at all, so you
| can't have component-local state. The framework reruns all view
| code at once, passing the current global state. You can
| approximate components by writing functions that slice and dice
| state and divide up the work, but that's transparent to the
| framework, and there's no place to store state besides the
| global state.
|
| These all have trade-offs. They may require more complex
| runtimes / toolchains, or simply shift around the burden on the
| programmer (what's easy/hard, what kind of bugs will be
| common).
|
| I'd love to see more approaches in this space. Not all trade-
| offs are right for all situations, and I'd like to see more
| ideas that meaningfully change the development experience,
| rather than "if you squint it's basically the same thing"
| ideas.
| jsmith45 wrote:
| > I think Solid is in this camp too, but I haven't used it.
|
| Correct. Solid is all about signals (reactive values). When
| you run any effect (rendering updates are effects created
| behind the scenes for you), it will get run once immediately,
| tracking which signals where called. Then it will subscribe
| to those signals to re-run the effect on change, and it will
| resubscribe to newly called signals, and unsubscribe from no-
| longer called signals.
|
| I believe that it is roughly equivalent to Vue's reactive
| api, except that rather than using a proxy or setters to
| allow object mutation to trigger effects or re-render, it
| uses separate update functions, more like react hooks do.
| WorldMaker wrote:
| I think most specifically the call out in the article is: the
| Hooks that React provides are extremely "low-level" (and
| intentionally so) and while you can do everything with just raw
| low-level hooks, consider returning to higher level hooks. The
| article provides some simple (Typescript type-safe examples) of
| higher level hooks. It also recommends that
| Redux/MobX/Relay/etc are still very useful higher level tools,
| even or especially in hook-based components in React.
| jitl wrote:
| Jetpack 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.
| colejohnson66 wrote:
| > ...or go back to class based components?
|
| Was there anything _wrong_ with class components? It 's what I
| learned half a decade ago, and the idea of a "state" object
| made so much sense. Now, with hooks and whatnot, it seems like
| React is trying to be "functional" without actually being so.
| mejutoco wrote:
| I would say mixing state and behaviour was the main problem
| with class components (and oop), although it might be a
| better problem to have than all the approaches supported
| simultaneously (some code with class components, some hook
| heavy, etc)
| adamddev1 wrote:
| I also liked/like the idea of one state object. With hooks
| now they really discourage packing everything into one state
| object and you either have to use a bunch of different
| `useState` statements for your different variables or pack a
| big state object in one `useState` but then you face big
| performance issues with needless re-renders when you only
| change one element of the state object.
| andsbf wrote:
| Take a look at useReducer(no 3rd package, part of the React
| api)
| codeflo wrote:
| I'll prefix by saying I agree with a lot of the criticisms,
| and I've experienced first hand how hard to explain and error
| prone hooks are. But for this post, let me defend the
| concept.
|
| The idea, in a very rough nutshell, is to allow separating
| behavior and presentation.
|
| Hooks are the reusable unit of behavior. You compose hooks
| into more complex hooks that might implement loading and
| saving data from/to the server, for example.
|
| Then you can use this hook with different components, or use
| the same component in a different context with a different
| data source. This can be very powerful if used well.
|
| But as I said at the beginning, hooks are unfortunately also
| very difficult to get right.
| HeyImAlex wrote:
| To expand on this, many of the things that hooks now make
| easy were bespoke per-class implementations with details
| that leaked into every lifecycle hook. Think of how you'd
| write a chain of useMemo calls in a class component to see
| just how bad it was.
| mostlylurks wrote:
| With class components, lifecycle management is trickier to
| encapsulate and reuse since you have to split such things up
| and scatter them across several different methods or wrap the
| whole component in a HOC, and in general class components
| require more boilerplate. Thus you'll usually end up with
| code that's just a bit more spaghetty than with function
| components. With hooks, you can at least encapsulate some
| specific behavior behind exactly one function call without
| jumping through any extra hoops.
| mejutoco wrote:
| Unless I am missing something, functional components and
| centralized state (a la elm https://guide.elm-
| lang.org/architecture/) are enough. Bringing global state
| (Context called from anywhere) can make it less clear.
| codeflo wrote:
| It's often not even that much manual housekeeping. If you
| follow the second advice, then ESLint will do a fine job of
| telling you exactly what's missing or superfluous.
|
| (I argue that ESLint is almost required when working with
| React. You can turn off its weirder other rules if they become
| an annoyance, but the hook rules are golden.)
|
| No, I think the problem is the combination of closures,
| mutability and identity. Very few other things in programming
| punish you that harshly (and subtly) if you don't have a
| crystal clear understanding of all three concepts.
| latchkey wrote:
| ESLint is fantastic, but what would be more golden is unit
| tests failing or code not compiling. It is almost like hooks
| need to be written in a meta language like JSX so that they
| can be compiled and thus failed when written poorly.
| mejutoco wrote:
| IMO 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 wrote:
| > 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 wrote:
| That 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 wrote:
| We 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.
| mejutoco wrote:
| I think probably you intended to reply to some other user,
| unless I am missing sth.
|
| I see a difference with hooks, where you need a linter to
| verify that you used them as intended: they must start with
| useSomething and be called in the top level. As opposed to
| use native language features.
| kaycebasques wrote:
| I 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.
| Toine wrote:
| It 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 wrote:
| Hooks 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
| chrisco255 wrote:
| No judgment, but what particular aspect of Next.js or React
| hindered you? How much JavaScript experience did you have
| before undertaking the task?
| kaycebasques wrote:
| I was on Google's Web DevRel team for 6 years. Owned all the
| Chrome DevTools and Lighthouse docs for 4 years and led
| content strategy for https://web.dev and
| https://developer.chrome.com for 2 years. Lots of experience
| building small applications in vanilla HTML/CSS/JS and I
| build toy apps in whatever frameworks are currently popular.
| I'm a technical writer by trade so I usually don't have time
| / motivation / business rationale to dive into a particular
| framework to the point of mastery but definitely am not a
| novice either!
|
| This comment [1] describes exactly how I shot myself in the
| foot w/ hooks. In my head this is how React talks to me now:
| "Tell me your dependencies. NO NOT THAT KIND OF DEPENDECY!!"
|
| [1] https://news.ycombinator.com/item?id=30754873
| izzygonzalez wrote:
| I 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.
| kazinator wrote:
| I'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.
| dgb23 wrote:
| useEffect 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.
| ncfausti wrote:
| I 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.
| wishawa wrote:
| I 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
| inglor wrote:
| I 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.
| erokar wrote:
| For me it seems React has made some unfortunate choices in an
| attempt to go FP in a language that does not afford it very
| well. Instead of embracing JS and working in alignment with its
| limitations/offerings, the React team has introduced elaborate
| workarounds that gives a whiff of FP but introduces indirection
| and verbosity and does not solve basic problems of state (i.e.
| the example from the blog post with classes vs. closures).
|
| I prefer frameworks like e.g. Svelte that works with what JS
| affords. If you want to go full FP Elm or ClojureScript will
| give you more value for your money than React imo.
| WorldMaker wrote:
| FP is a wide spectrum and JS can do some FP well. I'd argue
| the issue with what React is attempting with hooks is not
| that it is FP, but it is an attempt at particular "higher-
| kinded" monads when JS today mostly just has basic support
| for simple kinded monads (Promises/thenables and async/await
| syntax). The types of monads that hooks want to be are
| advanced and require a bunch of extensions even in "big FP"
| languages like Haskell. (In theory, if the biggest concern
| was no do-notation in JS, you could approximate it with
| async/await syntax. The mental model of async/await might not
| make sense to what you were doing with it, but the dualism
| with do-notation should be valid. What Hooks are trying to be
| needs things like GADTs and other extensions in Haskell from
| my understanding.)
|
| To me, I still think Hooks are useful for making that big
| swing attempt in JS despite needing so many crutches like
| hard education spikes in the learning curve and so many
| linters, but the problem with Hooks is definitely not that
| they are "FP" but that they are advanced FP that even FP
| still hasn't figured out all the bugs.
| purplerabbit wrote:
| I 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
| softfalcon wrote:
| MobX from a quick Google seems to resemble the old Knockout.js
|
| We use Relay to define our state management, trigger global
| updates across many components, and also query/mutate our data.
|
| Does MobX have a way to do the global state management that
| Relay does?
|
| I'm curious because after using Relay, I've found most other
| types of state management to feel extremely heavy with boiler
| plate code.
|
| Thoughts?
| brundolf wrote:
| One of the great things about MobX is that it works on plain
| data and plain operations on that data [0], not just the
| contents of a sanctioned Store, which means it has no
| opinions about where you put your data. Local objects
| captured by closures? Cool. Classes? Great. Mutating
| arguments? Go for it. Global singleton? Have fun.
|
| [0] Technically, it wraps all the different data structures
| in Proxies etc as soon as they get assigned into an
| observable structure. But the goal is for you to never have
| to think about them as anything other than plain data, and
| that abstraction very rarely leaks.
| jitl wrote:
| How 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 wrote:
| Because 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.
| eatonphil wrote:
| If Solid or Svelte had a way to import React components (for
| 3rd party code) and a documented migration path for 1st party
| code I'd be interested in switching.
| holler wrote:
| I was curious and indeed confirmed that ember.js/glimmer.js
| @tracked properties were inspired by MobX.
|
| From /u/wycats "The rule in Octane is: "mark any reactive
| property as @tracked, and use normal JavaScript for derived
| properties". That's pretty cool!"
|
| https://news.ycombinator.com/item?id=21848929
| brundolf wrote:
| +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
| memco wrote:
| In 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 wrote:
| kubb wrote:
| considered 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)
| simonlc wrote:
| News: 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.
| serguzest wrote:
| In 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?
| lxe wrote:
| > 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.
| bitwize wrote:
| No, "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.
| didip wrote:
| This 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.
| pandesal wrote:
| You 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.
| steve76 wrote:
| AgentME wrote:
| React 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!
| raiflip wrote:
| FP 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.
| hughw wrote:
| Using ImmutableJS [1] for all the state variables avoids the
| identity issues he raises.
|
| [1] https://immutable-js.com
| i_like_robots wrote:
| I 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.
| gitgud wrote:
| The 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.
| daok wrote:
| I'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 wrote:
| It's weird after using Mobx since 2017, that a framework seems
| revolutionary because it can figure out dependencies. React has
| really brainwashed us.
| jakelazaroff wrote:
| useEffect 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.
| steinuil wrote:
| The 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]
| akagusu wrote:
| I 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?
| serverlessmania wrote:
| What classes vs function have to do with OOP vs Functional??
| drc500free wrote:
| 17 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.
| leroman wrote:
| Maybe 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 (...)
| thrwy_ywrht wrote:
| Bugs 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.
| skydhash wrote:
| I'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.
| masylum wrote:
| The 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.
| paulmd wrote:
| in java terms, it sounds like you need to define your own
| "equals()/hashCode()". The "reference equality vs logical
| equality" is well-understood in that domain at least.
| skydhash wrote:
| Props are outside the boundary of concerns for a component as
| far as allocation go. You react to value and identity. The
| parent component is the one concerned with actually giving
| you the correct props. Very much like a function call would
| work. You can provide a signature or a contract, but you are
| not actually expected to deal with every kind of abuses,
| especially things that fall outside good practices.
|
| Perhaps it is a concern in bigger codebase. AFAIK, there is
| no method to enforce this kind of contract. But documentation
| could be a big help. Like documenting how changes to a prop
| impact the behavior of the component - like the common
| `initialValue` and `value`.
| andrewingram wrote:
| That's why a number of people (myself included) advocate for
| overzealous/defensive use of useMemo/useCallback as a means
| of ensuring that a prop changing is always meaningful. The
| rationale is summarised here:
| https://www.zhenghao.io/posts/memo-or-not
|
| There are good reasons for _not_ doing this, since using
| these hooks isn't free; and technically speaking useMemo
| isn't an identity guarantee (though it currently behaves as
| one), but I haven't experienced any of the common useEffect
| pitfalls since adopting this methodology a couple of years
| ago.
|
| But as my sibling comment points out, a lot of the need for
| this defensive coding would go away if there were more ways
| of defining equivalence. I hope that one day the record and
| tuple proposals land, which should help a bit. But i'd also
| like to see something like Python's __eq__ and __hash__ in
| JavaScript too - perhaps done in a similar way to
| [Symbol.iterator].
| clansimus wrote:
| Yea, 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 wrote:
| Exactly.
| bstar77 wrote:
| This was my overall thought as well. When I get myself into the
| type of trouble he's describing, it's usually because I either
| designed the effect poorly or it's simply doing too much. I
| also find that I'm abstracting certain complex effects into
| Redux Sagas more and more which solves some issues around
| effects depending on the result of other effects- not all
| processes should be triggered in this way.
|
| It took me a long time of grinding on various effects scenarios
| to figure out efficient, easy to understand solutions to
| complex behaviors. That said, I do agree with his points on
| under/over subscription... that is still something that
| frustrates me, especially when the linter wants me to
| complicate something that seems unnecessary.
| jchook wrote:
| Kind 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-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.
| andrewstuart wrote:
| OK so criticisms heard.
|
| What is a solution?
| chrisco255 wrote:
| Read 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.
| martimarkov wrote:
| I 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://factorial...
| PaulHoule wrote:
| Nice 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.
| nightski wrote:
| The 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 wrote:
| > 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.
| bern4444 wrote:
| > Hooks are the antithesis of this - they create code them
| seems pure
|
| I disagree. The presence of a hook is the indicator that
| something impure is happening. Seeing a hook should be
| equivalent to seeing a promise, option, IO type etc.
|
| Hooks also compose beautifully together. You can make so
| many great new hooks by combining just useState and
| useEffect together, bundling up that functionality into a
| new hook that you can then use in any UI.
| horsawlarway wrote:
| > The presence of a hook is the indicator that something
| impure is happening.
|
| Yes. And that's my whole point.
|
| React was very powerful when care was taken to place
| impure code into a single class based component, that
| then passes state down to pure components as props.
|
| React is a lot less powerful when developers scatter
| hooks _everywhere_.
|
| New developers no longer have to go out of their way to
| understand the render lifecycles of a class based
| components, and feel the pain of writing
| componentDidMount or componentWillMount or
| componentWillUnmount or shouldComponentUpdate functions.
| Instead they just throw a hook in. Which is mostly ok -
| but it's hiding that you do actually still have to care
| about how this whole shindig works (and opens up a whole
| new world of pain around identity and equality checking,
| re-render cycles, dependency passing, etc)
|
| I'm not saying hooks don't have an upside (ex: I'm right
| there with you, I mostly prefer a hook to an HoC from a
| reusability stand point) but hooks let developers shove
| their head into the sand and mostly pretend that they're
| writing a pure function - and they're ABSOLUTELY NOT.
| nightski wrote:
| There was nothing preventing you from scattering state
| everywhere in class based components. On top of this the
| component tree became a huge mess of HoC's stacked on top
| of each other.
|
| You absolutely should not be scattering hooks everywhere
| in your code base. The same principle applies to use them
| higher in the hierarchy and pass down props.
|
| This is a simple principle that can be taught to a new
| React developer. Keep your state at the highest level it
| makes sense to no matter the state mechanism used.
|
| Hooks allow for composition of effects in a way that
| class based components did not.
| horsawlarway wrote:
| > There was nothing preventing you from scattering state
| everywhere in class based components.
|
| There was though - it's the same pain you're referring to
| later... "Hooks allow for composition of effects in a way
| that class based components did not."
|
| Class based components sucked in a _lot_ of ways. But the
| nice side effect of that was that folks tended to use
| them more carefully, and avoid using them when they didn
| 't understand them (or at least avoid implementing any
| method besides render()).
|
| I'm not saying hooks don't have nice properties - I'm
| saying that I'm not convinced (after using hooks for
| about 2 years now) that the price you pay is worth it.
|
| The number one source of bugs in our codebase is...
| drumroll... hooks. I think a part of that is that state
| in general is evil, and will be where most of the bugs
| lurk. But I think the other side is that hooks have a
| completely new, unintuitive, hard to reason about set of
| rules. Composable? Sure, sometimes, if you work really
| hard to understand exactly what sort of new rules you're
| creating and then hiding in their complexity. Intuitive?
| Fuck no!
| nightski wrote:
| We can just agree to disagree then. I'd find libraries
| all the time on github which had class components using
| state in weird ways you wouldn't expect.
|
| It sounds like your org could use some simple guiding
| principles and code reviews. You seem experienced, this
| shouldn't be a big problem. Maybe help guide your junior
| devs?
| horsawlarway wrote:
| Eh - I'm not really sure we're even disagreeing. I just
| think that hooks let you stack the abstraction tower a
| lot higher, and answering some fairly basic questions can
| become really hard.
|
| There's power there, and I absolutely agree that hooks do
| a better job of making for re-usable code than HoCs, I
| just think that the general level of understanding for
| them is low, and most devs do a really poor job reasoning
| about them (and in generally - I find they're basically
| impossible to reason about in isolation).
|
| I see people do things like wrap everything in useMemo
| and useCallback, or pass complex objects to useEffect as
| deps, or fail to understand that making the output of
| useState the dependency of a useEffect hook that happens
| to call the corresponding setState function is a recipe
| for lockups, or any number of other fairly simple
| mistakes.
|
| Plus... tools like redux strongly encourage destructuring
| semantics, and destructuring for hooks is absolutely the
| wrong thing (for the same reason - equality and identity
| checks). But then you're in a conversation about object
| identity and memory locations with a dev who has never
| encountered a pointer in their life, who's 6 months out
| of a bootcamp, and whose eyes are glazing over further
| and further with every word out of your mouth.
|
| Worse - hooks can give you a loaded gun if you expect all
| the environments your code runs in to act like a browser
| (see my useLocation example with JSDom). Works a-ok when
| tested in a browser. Will even work nicely for the
| specific tests you might write for your component (since
| folks generally mock their hooks) but will absolutely
| foot-gun you if another spec calls the real hook. Happens
| to eat up a boatload of CI cpu usage and time as well.
| PaulHoule wrote:
| Some things are easy to express as functions (compilers).
| Other things aren't (user interfaces).
|
| Even when immutable data is easy and is good from a
| software design perspective it is often a terrible choice
| from a performance perspective. Advocates say the
| performance loss is just a factor of two in many cases but
| that's why FORTRAN survived so long against C, why people
| are developing Rust when Java is available, etc.
| horsawlarway wrote:
| I don't disagree with you at all.
|
| There's a reason no on is writing modern games in
| functional languages, and that reason is performance.
|
| But that said - At least for me - the major attraction of
| React was that it really concentrated on making ui
| related code pure. Give a component the same props, and
| you get the same DOM.
|
| That's a really powerful concept for reducing bugs,
| easing testing, and giving you composable components.
|
| It is not a performance improvement.
|
| I think hooks really hollowed out the value proposition
| here. Because class based components were more painful, I
| used to see a lot of care and thought put into
| consolidating the logic that generated props into a
| single class based component (consolidating state). That
| component would then mostly pass down props to pure
| components.
|
| Hooks make it easy to just throw state into any old
| component - which is nice in some sense, but like I said
| - it hollows out the value proposition of having pure
| components.
|
| Good teams will still try to write mostly pure
| components, but many folks will just liberally scatter
| hooks into their code, creating code that becomes
| increasingly hard to reason about.
| horsawlarway wrote:
| I agree.
|
| I've had several conversations where fans of Hooks will justify
| them by saying that "functional programming is about
| composition over inheritance".
|
| And I think that's entirely missing the point of functional
| programming. The goal wasn't to remove inheritance in favor of
| composition, it was to remove _STATE_ - which in turn results
| in the nice property that functions can be composed, because
| they take all relevant data as arguments (they are pure).
|
| Hooks basically blow that away - you've added back in all the
| problems of local state, but now you've hidden it behind a
| brand new paradigm that developers just don't have a very good
| feel for (even years after the introduction of hooks).
|
| I'm reasonably well-versed with hooks, and even I find myself
| having to do incredibly complicated and deep dives into
| upstream code to answer simple questions, like "How many times
| will this hook run?" or "How many render cycles will this hook
| introduce?".
|
| Sometimes the answer is so far upstream it's basically
| impossible to answer without running code - Ex: if you depend
| on the "useLocation" hook from react-router-dom, and you pass
| the entire object as a dep to useEffect (which is a mistake in
| and of itself), you will be fine in the browser, but Jest tests
| will trigger an infinite render cycle, because JSDom generates
| a new object for each call of window.location.
|
| I can reason about functions that are pure, and that's the
| freaking point of functional programming. I cannot reason about
| functions with hooks in them - it's FAR worse than class based
| components in basically every way _except_ ease of re-use.
|
| I think in many respects - we threw out the baby with the
| bathwater.
| whoisthemachine wrote:
| > And I think that's entirely missing the point of functional
| programming. The goal wasn't to remove inheritance in favor
| of composition, it was to remove STATE - which in turn
| results in the nice property that functions can be composed,
| because they take all relevant data as arguments (they are
| pure).
|
| I came here to see this said...regardless of the method used,
| _state_ is what is challenging to maintain, regardless of how
| your framework or tool modifies and tracks it. And the only
| way I know of to properly wrap some sense of sanity around
| complex state modification is with unit tests, again,
| regardless of framework /tool. If you can't test it with a
| unit test, then you're going to struggle manually testing it
| as well, even if it does usually work.
|
| A side-note is that I always thought the obvious split for
| functional/class-based React components was
| stateless/stateful (as full-blown objects are basically
| purpose-built for tracking state), so I was surprised when I
| joined this new project at my employer and learned about the
| interesting world of hooks. I rarely dabble in React however.
|
| My snarky side today wants to add "developers struggle with
| maintaining state, what else is new".
| [deleted]
| ajkjk wrote:
| No 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.
| ezekg wrote:
| I'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.
| i_like_robots wrote:
| This article helped me a lot, it's still a large amount of
| information to internalise though: https://medium.com/the-
| guild/under-the-hood-of-reacts-hooks-...
| brimble wrote:
| I 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 wrote:
| Because reusing code in those class based components was
| never as easy as creating a new hook.
| barbecue_sauce wrote:
| I haven't really used React much since hooks were introduced
| (not because of hooks, just a coincidence). Up until that
| point, I had been using React almost daily. At the time, I
| remember thinking "What is this for? What problem does this
| solve in my already well-architected and organized front-end?"
| dkarl wrote:
| For 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.
| purplerabbit wrote:
| You 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
|
| 2. https://github.com/alexreardon/use-memo-one
| codeflo wrote:
| Two thoughts.
|
| One, you don't rely on a semantic guarantee if you use useMemo
| for derived state. Avoiding rerendering counts as an
| optimization as far as the React docs are concerned (your
| program works if there's an extra render), and this is in fact
| exactly what it was intended for. The docs you linked seem to
| agree: Regardless of whether an offscreen component keeps or
| doesn't keep its useMemo, the code is correct and there's at
| most one extra render.
|
| Second, while I agree with the verbosity complaint, I
| personally make a point to use useMemo as coarsely as possible.
| It's often completely fine to compute all derived state in a
| single big lambda that returns one big (immediately
| destructured) object. It's only when you have multiple pieces
| of derived state that update individually and are also all
| expensive to compute that you actually need fine-grained
| useMemo calls. And in this case, you can always think about
| extracting sone of that logic into a helper function/hook.
|
| It's not perfect, but I think it's possible to avoid a lot of
| the pain most of the time.
| purplerabbit wrote:
| I'm with you on thought #2. Regarding your first thought,
| however: if you want control over when `useEffect` callbacks
| fire, identity isn't just an optimization, it's a necessity.
| For example (used in another comment): 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
| codeflo wrote:
| Oh, that's a good example. I'll argue that you should try
| to use primitives (strings/numbers) as keys in those cases.
| But if you can't, then you're right that identity is
| critically important.
| acidbaseextract wrote:
| I'm curious if you've used useDeepCompareEffect, the use-
| deep-compare-effect npm package? I've found that it is
| pretty reasonable foolproofing for many of these identity
| questions. I'm well aware of Dan Abramov's objections to
| the deep equality checking [1] but I still find it a bit
| easier for me and other devs to reason about when doing
| things like data fetching.
|
| [1]
| https://twitter.com/dan_abramov/status/1104414469629898754
| purplerabbit wrote:
| Hadn't heard of that -- thanks for sharing!
|
| Crazy how many choices there are in the mix (use-deep-
| compare-effect, the `JSON.stringify` approach mentioned
| by Dan, `useMemo`, and `useMemoOne`). Feels like a "pick
| your poison" scenario, as each one has a significant
| issue.
|
| That being said, `useDeepCompareEffect` does seem the
| most "foolproof", and "foolproof" is probably more
| important than intuitive or performant in most cases.
| srcreigh wrote:
| Why do you need identity except for increasing performance?
| purplerabbit wrote:
| Identity 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 wrote:
| Though 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 wrote:
| Maybe 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.
| djbusby wrote:
| Nah, "considered harmful" is like the OG click-bait; intended
| (IMO) to spark discussion.
|
| https://en.m.wikipedia.org/wiki/Considered_harmful
| masylum wrote:
| yes, it was a wink/homage to historic discussions on anti-
| patterns :)
| Igrom wrote:
| I 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 [1]
| https://news.ycombinator.com/item?id=9744916
| quxpar wrote:
| I 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 wrote:
| I 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 wrote:
| In that example, eslint will tell you to add `params.teamId`
| as a dependency, not `params`
| pictur wrote:
| The 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.
| Tade0 wrote:
| > 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.
| kietay wrote:
| > 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.
| koprulusector wrote:
| _facepalm_
|
| > 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.
___________________________________________________________________
(page generated 2022-03-21 23:01 UTC)