I absolutely love hooks and don't want to use the `React.Component` class ever again.
And yes, hooks need to be grasped, they're quite something else. But once you get the hang of hooks, they're very simple to understand.
OP seems to be a bit stuck in a hole.
If you end up in a situation where your hooks come loose and the code becomes a mess — just delete them all and rethink your code / component logic structure. Believe me, it all can be simplified. Just rinse and repeat until you are satisfied with the outcome. That's when the "aha!" moment comes.
At the moment I only use `useEffect`, `useState`, `useRef` — the rest is unneeded so far even in my large-ish application.
There are some weird edge cases with useEffect, no question.
I don’t think they’re bad enough to want to go back to using classes... but I think the OPs post was a bit more specific than you’re giving them credit for, and they stuff like useAsyncEffect exists because it is a common pain point.
The behaviour is weird sometimes; just because you can use the trivial use case easily doesn’t mean you won’t stumble, and if you do, good luck trying to understand what went wrong.
> ...they're very simple to understand...
They’re not simple.
They just superficially appear simple, which is good enough most of the time.
I think it’s entirely fair to complain that’s not ideal... even if you still think there’s a major benefit to the trade off compared to classes (which I do think is true too).
...but I’m very sympathetic to people hitting edge cases.
On this particular point, the reasoning is that refs are meant for values that don't need to trigger a rerender[1] -- an escape hatch, rather than something you reach for by default.
In that sandbox, you can accomplish what you mean to accomplish by using state rather than a ref.
I've seen a ton of people make that mistake, though. I think it's because what you write is accurate - "refs are meant for values that don't need to trigger a rerender" - but most people think of refs (or at least refs of DOM elements, which for many people are the only refs they ever use) as just a way to access a DOM element. They expect it to just be a DOM element in a normal variable.
I'm not sure if there is a less confusing way of doing it, but it's damn confusing.
But it's a pretty straightforward rule once you know it right?
This is simply a matter of reading the docs (or making the mistake until you learn). Not an inherently more complex characteristic than, say, Class component lifecycle methods, which also required understanding their rules and reading the docs.
Once you know refs updates won't re-render, you won't make this mistake.
I don't understand why "a thing you need to learn then you're good" is somehow inherently more complicated than ... other apis that you need to also learn.
> but most people think of refs as...
This sounds like Dan Abramov's comment that some of the struggles with hooks is from people who have experience with react without hooks, vs people coming new to the whole thing. So maybe it's about relaxing pre-conceived notions until experience takes over.
Tons of shitty "here's how to use hooks for X" articles on Medium make the mistake, so people learning hooks right at the beginning will not be completely immune.
Also that the fix is "instead of using this one kind ref, use another kind of ref and put it in state"... I don't know, like I said I'm not sure if there is a better solution, but it still feels kind of unintuitive and complicated.
Now that I'm thinking about it... what is the reason that DOM refs and other refs need to be handled by the same concept? Every time I make a DOM ref, I'm doing something with it in a hook like useEffect. Why make me jump through hoops to re-run the hook if the DOM ref changes?
(I recognize there are probably good answers to those questions, the React folks are great, I just don't know the answers! Is it just to avoid introducing one more "type of thing", and instead making refs and DOM refs the same "thing"?)
There's always need for escape hatches and maybe I'm missing your use case, but in the context of a discussion about how hooks are more complicated than classes, what were you doing before with refs to solve your "re-render" on change scenario?
Your example was likely contrived, but modifying innerHTML should be replaced by putting whatever in state and simply rendering it. And use state for dependencies where you want to re-run on change. Refs are just another way to keep state but not have it affect render cycle.
If you're not aware of that, it is very tempting to use `useRef`, which is what I have often seen. Before hooks, we did not have the temptingly-named footgun `useRef` for this scenario (although we did have other footguns for other scenarios, and overall I love hooks).
I suspect this may be one of those cases where there's an 'aha' moment involved, and people who get that moment early find it simple to understand, people who haven't got to that moment yet are going argh, and people who got to it late sigh and/or write "yet another X tutorial" in the hopes that how they got their aha moment will work for other people.
The classic X here, of course, is monads.
But as personal examples, it took me ages of headbanging to get the 'aha' moment for git, even though a bunch of people I know got it almost immediately.
In the case of hooks, once I built a mental model of how they were (theoretically) implemented, I went 'ooh' and found them obvious and relatively easy from then on. It looks to me like the author of this piece didn't have that luck, and so for him, they're not simple (although maybe later, post-aha-moment, they will be).
tl;dr: "They seem simple to understand once you understand them, but are mystifying up until that point" seems to me to be the most likely hypothesis here.
I think you can make that first useEffect block fire by passing in an empty array as your params. This should run the code block on mount. To run a function on unmount, return it from the same useEffect with the empty array params.
My frustration with this is that it's completely unintuitive and doesn't read very well. Yes, you'll get used to it once you work with hooks enough, but at first glance, the empty array and the return value from the function passed to `useEffect` don't give me any useful information as to what the code is actually doing. It's an entirely React-specific abstraction that forces you to memorize a bunch of obtuse rules in order to use it effectively, as opposed to building on simpler primitives, which had previously always been the appeal of React.
Have you seen the ReactConf talk on hooks? [1] It's absolutely worth the watch, even if you're somewhat familiar with hooks. They go through the pitfalls of class-based components and how hooks solve them.
It more-or-less boils down to: 1. class-based components force your lifecycle logic to live in disparate locations. 2. Class-based hooks are obtuse with hidden gotchas whereas pure functions tell you exactly what they're doing. 3. A whole class of unergenomic solutions disappears when you use hooks.
I'd argue that hooks didn't really make anything better, they just replaced the pitfalls with different obtuse pitfalls and unergonomic solutions.
Hooks have their share of weird issues; they explode if they're called in a different order/number from how they were called the first time the component rendered (making them not at all pure functions), and it's super easy to screw up the dependency array to useMemo/useCallback (so either your hook is useless and re-installs the event handler on every render, or you get stupid "the callback had a stale copy of the state" bugs not possible with class-components).
I've also seen my fair share of situations where what would have been a this.setState with a callback becomes a tangled mess of useStates and useEffects, or someone bends over backwards to try and avoid re-installing an event handler every time one of the props or state variables it uses changes.
I feel of all the arguments against hooks, the whole “order of hooks” thing is the thinnest. You just learn about it and then it’s not a problem again.
I definitely get that there are some more complex cases where you have to jump through a couple of useRef hoops to do what you need. On balance though, I think the way the behaviour becomes declarative is worth the trade off.
Coming from the FP world, hooks are a terror because they break all the nice rules functions are supposed to uphold. Hook components don't necessarily return the same value when given the same arguments, making them impure (stateful). The benefit of function components IMO has always been that you can use the function scope as a container for pure, stateless rendering logic. With the introduction of hooks I can no longer ensure that.
Nothing stops you from using pure function components, and you should where possible. But eventually, you need to hold state somewhere. You don't need to put it in your components, but you do need to deal with it. If anything, hooks are a nice middle ground where the behaviour becomes declarative.
My personal opinion is that state should be contained in some sort of class or data structure, separate from functional architectures where the notion of state is generally avoided. So my usual suggestion would be to use either a class component (or redux or insert favorite state mgmt solution here) for your state, and then inject the state as props to your function components
That's fine, you can use React's Context API and reducers just like Redux, for which the useContext and useReducer hooks exist. Then it's just like Redux, inject a Provider and Consumer and you're good to go.
They don't, but before I could look at a function component and go "ah, a nice stateless component" whereas now I have to wonder whether the _function_ contains _state_
The issue I have with the "rules of hooks" is that it makes some previously trivial things cumbersome at best, and downright complicated at worst. For a quick example, something as simple as mapping an array onto a series of elements with a callback requires (as far as I can tell) that the inner element be broken out into its own component that calls `useCallback`, since you can't create a unique callback for each item in the parent.
I don't quite get the obsession with the order of hooks... You call all the hooks in your component function and don't optionally do so. In the end, if you're optionally calling different hooks differently in the same component, you're probably doing something wrong, and/or they should be separate components.
I've found that the hoop jumping type stuff, while potently annoying, can at least be abstracted into a custom hook (and then mocked in tests) easily so that the code is clean.
It's not classes themselves, it's the lifecycle methods, the constructor, this, state, and it all interacts. Hooks let you do more or less the same thing, but IMHO with a simpler and clearer API.
You have more control too. Look at the parameters to useState to see what I mean.
I feel like there is an alternative world that we aren't seeing, where instead of changing the paradigm, the react team redesigned the class API instead.
The class API lifecycle methods are kind of a mess, and useEffect() seems like a more convenient abstraction. But I could imagine replacing the lifecycle methods with hooks that are added at object construction and get similar benefits. I wonder if the problem is not so much classes, but just the API that happened to evolve.
How are your functions pure if inside them you call `useEffect`, `useState` ? Where is pureness when at first look they generate side effects, or am I missing something ?
Sure, if you define a function called useWhatever that doesn't actually call any hooks, that might be a pure function. But the restrictions described in https://reactjs.org/docs/hooks-rules.html are not restrictions that pure functions have.
> If you end up in a situation where your hooks come loose and the code becomes a mess — just delete them all and rethink your code / component logic structure
“Just burn it to the ground and start over,” isn’t an inspiring endorsement of the methodology...
`useContext` is excellent. React.context is a solid alternative to the likes of Redux and Apollo, but takes a fair amount of boilerplate to add to a class based React component. But with `useContext`, applying global state is a breeze. A lot easier to manage than Redux, at the very least.
The only real dislike I have, is in how `useEffect` confuses the React lifecycle. It took me a little while to grok the lifecycle idea, but it was/is a useful way to conceptualise what happens to your components at runtime.
But now, you have to remap all that knowledge to the hooks workflow. It's not a huge challenge, but it's yet another overhead.
Indeed, starting out, our team ran into a number of issues with infinite rendering loops and the like. Took a fair amount of reading to discover where the pitfalls were.
I understand how contexts can replace Redux, but can you please elaborate how they can be used instead of Apollo? Do you mean the whole of Apollo or Apollo local state?
We're slowly refactoring our class-heavy React app to functional components with hooks.
One thing we haven't been able to replace, though, are classes that depend on refs. How to do this, since there's no instance with functions like there are with classes?
I was extremely skeptical of hooks in the beginning after having used class-based lifecycle components for a long time. Overall, I _think_ it's been a net positive but I end up feeling that I'm writing more code at the expense of using well defined constructs (lifecycle methods).
I usually feel like I'm writing too many effects which update state(s) and the lifecycle flow becomes harder to contain mentally, however there are times where I'm like "this is definitely easier or less of a cluster then it use to be".
So I think the jury is still out. In terms of Hook's initial premise of "we're doing this so that developers unfamiliar with class-based OO paradigms can work better/faster", I don't think it added any more clarity or ease-of-use over the class based lifecycle methods tbh.
Maybe this is because I'm a "developer[] unfamiliar with class-based OO paradigms" but I find hooks components easier to read later. You start at the top, read to the bottom, and assuming you can catch crazy indirection within a hook callback during review, that's what happened. No HOCs. No having to memorize an externally documented lifecycle order. No having to cross-reference methods against the state they update 50-60 lines up the file.
I'm genuinely curious if the difference is because I was doing tons of Recompose/HOC style components before hooks came out.
Also, just FYI, it didn't click for me until this tweet[1]:
> The question is not "when does this effect run" the question is "with which state does this effect synchronize with"
Many people aren't event aware of the unnecessary renders caused by using hooks. If your app is so tiny that it doesn't matter if everything renders all the time, then you might not be aware of your `useCallback` recreating callbacks way too often (https://github.com/facebook/react/issues/14099), or that you're not even using it in the first place. Considering that front end is probably the area of software development that attracts the largest share of people like me - self taught, no computer science background - I'd also assume that many people love the perceived simplicity of hooks without realizing that it makes their code worse.
It'll only become apparent when you app is large and complex enough that it suddenly starts to matter when things re-render and then you'll be faced with tracking down those re-renders and fixing dependency arrays everywhere.
With classes, the naive solution is the right one when it comes to preserving function identities across renders.
Not saying hooks are bad, but they're not all roses and sushine.
A lot of people don't understand that React's default behavior is to re-render _everything_.
When a component is rendered, React will recursively re-render all descendants of that component. Out of the box, React doesn't do any optimizations like "skip rendering this component if the props haven't changed". Because of that, "re-creating callbacks" isn't an issue in base behavior [0], because there's nothing that cares if it's a new function reference or not.
It's only when child components are attempting to optimize perf by comparing props and avoiding re-renders that having consistent callback function references matters (ie, `React.memo()`, `PureComponent`, and React-Redux's `connect()`).
Yeah, React seems to have been designed under the assumption that DOM operations are the only thing front-end code can do that takes non-zero time. Like, here's the first line of their document describing how to use to improve performance using things like PureComponent: (https://reactjs.org/docs/optimizing-performance.html)
> Internally, React uses several clever techniques to minimize the number of costly DOM operations required to update the UI. For many applications, using React will lead to a fast user interface without doing much work to specifically optimize for performance.
My experience with React applications (both writing them and as an end-user) hasn't really borne this out.
This was also a huge part of the marketing push for years: if you listened to proponents, you have thought that the average app was bottlenecked on DOM updates, and that partial updates were too hard to do with anything else.
Could you elaborate? Rendering perf is pretty much the least of my concerns, even on my apps which support IE10. Most "perf" problems I find are related to fetching and appropriately caching data.
It's pretty easy to run into render performance issues with React, especially on older machines, when you have a very dynamic UI. At work, we dynamically generate several SVG charts and "badges" on certain pages. Putting even one of those onto a page without any optimization created about a half second delay between actions on my high-end machine, so we had to spend some time making sure that things weren't re-rendering needlessly and that DOM nodes were getting reused. But even if that seems like an atypical use case, we had similar issues rendering complex tables, purely due to the amount of DOM nodes needing to be put onto the page at once. So it might not be an everyday kind of problem, but I don't think it's uncommon to run into performance issues with React.
Not the parent, but here's one that I hit recently:
I hoisted the state of an input box up a few layers because other parts of my app are affected by the current typed content. Since I'd been careless passing closures in as props to expensive components, they were getting re-rendered on every keystroke. It was noticeably sluggish.
> re-renders that having consistent callback function references matters
That's the case I was talking about, I probably should have added that. In our codebase at work most components are using such optimizations but of course that's not the case for most people.
I have been working on an enterprise React App for the past 6 months using only the hooks API. The App is a non trivial Dashboard, displays various custom charts (done using d3 and manual SVG Elements in React) and some larger tables/forms. While I use useEffect() frequently, there is not a single useCallback/useMemo in the code.
I just profiled it on my 5 year old MacbookPro, and out of 50 commits or so, just a single one spiked above 20ms (which was the first commit, subsequent to same view were much faster) and almost all are in the <5ms range. I decided it is not worth doing any optimization yet, and I will defer useCallback/useMemo optimization until there is a real, noticeable delay in rendering.
> This is yet another JavaScript paradigm to learn. For the record, I am a 49-year-old React fanboy. I am a freelancer and use other frameworks apart from React, and this gives me fatigue.
Amen to that. The fact that still are in "here's a 'better' idea, let's try this" landscape in JavaScript is depressing.
I no longer jump on these new frameworks when the bandwagon flies by. I ignore postings for jobs saying they are rewriting their system in "new Framework Y!". I don't care how "pure" your new design is, I care about having it work well in the trenches.
React Hooks and Containerizing Everything are the things this old "Webmaster" is kicking down the road until all the bugs are shaken out. I also skipped gulp and grunt and jumped straight into webpack, which I've read far too much about to understand so little.
Interesting way of thinking - hooks _are_ a new framework, they are just marketing it under the same name.
I guess I'm old too. When someone shows me what (real) pain hooks solve on non-Facebook-size codebases, I'll be all ears. Until then... Thank you, classes work just fine and make my code nice and readable.
Overall I think React hooks are an improvement. My codebase is usually shorter and there is a lot less typing involved.
But hooks creates abstractions the developer needs to deal with that never existed before.
Hooks are not functional because they break referential transparency of functions.
You have to track dependencies manually and hooks are more difficult than they need to be for the "componentDidMount" equivalent. If you don't get the dependencies just right you end up with things not firing or in infinite loops.
You have to wrap your functions in "useCallback" or "useRef" just so the reference doesn't change and cause infinite loops.
You can't create abstractions where a hook calls another hook. So you end up having to inline a bunch more code into your function rather than outsourcing it into a helper function.
The positional order seems like it would be easy to work around if they allowed you to pass in a key. Not sure why that isn't available.
> The positional order seems like it would be easy to work around if they allowed you to pass in a key. Not sure why that isn't available.
IIRC it's because they implemented their own method lookup table (!) to associate with the Component object (!) but as a FIFO queue, more or less. I assume either for ideological (that's how they wanted it to work) reasons or because they (probably correctly) reasoned that loading down React apps with more strings at such a basic level would risk performance/memory problems. Plus if they did that then it'd really look like a method lookup table and be more obviously Rube-Goldbergian than it already is.
The primary concern was transparent composition and enabling "custom hooks". All of the "named/keyed hooks" proposals from the community failed that test.
Custom hooks naturally fall out of the current implementation. Technically, React doesn't even know that custom hooks exist - it just knows that more of the primitive hooks are being called inside your own component.
If you've got time, skimming the original React Hooks RFC https://github.com/reactjs/rfcs/pull/68 ) is informative (and admittedly difficult, because there's hundreds of similar comments).
They can make the string part optional. Variables and symbol tables have been staples of programming languages and compilers for ages now. It's a very standard pattern. Nothing Rube-Goldberg about it.
But React has deviated so far from both the OOP, FP, and traditional programming paradigms that now it kind of feels like hacks are needed to compensate for hacks.
I mean at the point you're writing a symbol table and associating it with an object so you can figure out which method to call in its context, probably it occurs to you that you're entirely re-creating a feature the language already has (but slower and worse) rather than just mostly doing so, as they are now.
I never understood the need for hooks and how it's used (the example that react doc has for useState is too simplistic for any useful pedagogical reasons) until I saw divjoy's generated code and studied how it used hooks. Then everything clicked and I've been more productive using hooks than I ever had with react classes. I can attest that cognitive load is lower with hooks than with classes.
I don't agree with the author's statement that there are only two (very involved) solutions to the pagination problem
(re: section titled "React relies on the order in which Hooks are called").
It can easily be solved by just setting the state in the event handler, since the state is included in the dependencies array for the fetch effect:
> On line 23, the useFetch Hook will be called once on the first render. On lines 35 – 38, pagination buttons are rendered but how would we call the useFetch Hook from the event handlers of these buttons?
We can call `useFetch` again by triggering a re-render. This hasn't changed at all with the introduction of hooks: new props or new state still triggers a re-render.
I wondered about this too, in general I think the article misses that updating state is the key to "causing" effects. Rather than latching onto the callback model to explicitly "run" effects.
Though I'll admit getting the relationship of state and effects caused by changes to that state can be more difficult to work out mentally, I think it results in a more complete understanding of what a component actually does.
I spent maybe 1.5-2 years working on a medium size React app and after taking maybe 6 months off, I came back to it. I started reading about React Hooks and honestly I did not fully understand them. Maybe it's just JS fatigue and me feeling like I'm trying to building a house on quick sand. Everything is always changing...
Recently I built a bolierplate JSON-api web app project in vuejs and golang (previously I was using TypeScript, Apollo GraphQL and Express). I find the terminology around vuejs much more user friendly and it comes with a router and state management built in...I try to avoid using any other JS module. The golang ecosystem is much more stable than nodejs which is a relief....I have a feeling I might just settle on vuejs + golang for all future web apps
We’ve writing most new code with hooks (including the Apollo GraphQL hooks like useQuery and useMutation). Generally been really pleased with them but did get bitten hard the other day.
The value returned from useState is just a plain ol’ variable like any other so it also gets captured by closures like any another variable. We had an editor that updated some state and then an event handler (defined as a closure) that did something with the state variable. The state was updated as expected but when the event fired, the closure had the original value. It can be worked around via a container/useRef (and is maybe a code smell in the first place) but it made for some painful debugging.
Interesting to hear someone who clearly tried to use it seriously. That said I do agree with the first comment on the article itself, he is using his "useFetch" in a very far fetched (haha) way. A hooks should not be called as a callback to an event, you should "trigger an action" (whether redux, a callback from a higher component etc.) that update a state which in turns trigger the hook. I understand this can be length / annoyingly administrative but that is the cost of the react architecture, with the payoff being to better understand the flow of the application.
I use redux and react-saga for more complex flows, sagas translate into generators and make async code flows look almost like synchronous code which is nice.
Sagas are a great power tool... and 95% of Redux apps don't actually need them. Using them just for basic data fetching is overkill. Sagas are most useful when you need to do complex async logic, like cancellation, debouncing, and decoupled "background-thread"-like computations.
Thunks are sufficient for most use cases, but the main thing they can't do is respond to dispatched actions.
For more info, see the Redux FAQ entry on choosing an async middleware:
useEffect is such a weird name. I get that it's kinda general in usecases but whenever I see the name my first thought is "what the hell is an effect and when do I use them?" and this is coming from someone who has actually heard about effect systems. Going from componentDidMount which is a nice, clear name that dictates when the function is run, to useEffect(() => console.log("Mounting!"), []) is so confusing. I'm not super thrilled that the difference between running on mount, on new props and on unmount is implicitly determined via the second argument or returning a new function. I get that useEffect has a nice coherence and is more general but that's not always a good thing.
Not to mention the docs for useEffect just mention the one usecase of running on mount and on update and neglect to even talk about running something just on mount.
That being said, the reducer hook is amazing and a great replacement for overly complicated setState or overly simple Redux stores. Definitely reminds me of ReasonReact in a good way.
What's lost in all this is that classes are simple and understandable. Introducing all these new paradigms removes that simplicity. Everyone is ready to hate on classes but they have been robust structures for a long time
I think it's fair criticism to say that hooks are extraneous to JavaScript. However, they are still a better tool for the job of building components, and they solve real and important issues around safety, maintainability and complexity. There are alternatives to JS, and alternatives to React in JS. I think it's good the React team chose this unique direction to make the best framework possible by taking advantage of JS's strengths to workaround it's weaknesses.
I think that what is lost is that Javascript is more of a functional language than an object-oriented one and a move to hooks embraces its functional roots while eliminating numerous footguns (this), simplifying reuse, and reducing redundancy.
I'm done with React. They keep re-inventing the wheel, and every iteration is full of issues that need to get resolved with yet another design principle.
I'm also frustrated with the introduction of a completely new paradigm, but to be fair to the React team, hooks are really innovative and don't re-invent the wheel at all. I, for one, have never used another framework that had anything similar to hooks.
Coming from a ClojureScript / Reagent background, functional components and hooks look very natural, useState being largely equivalent to reagent.core/atom. useEffect and useRef look like a logical extension of the same idea.
Hooks are absolutely fantastic. However, there are still a few pain points:
- useState with an array is bad news if more than 1 component is consuming or setting the state.The clunky alternative is to keep it in a ref & call a forceUpdate whenever you would normally call your setter
- useCallback doesn't scratch the itch for things like document event listeners since everything inside the callback will be stale. The clunky alternative is a custom useEventCallback that keeps your callback function in a ref. (and that might not work in the upcoming sync mode)
- linter rules can be too strict & the --fix flag can actually cause a break in your app by adding things to the dependency list. Sometimes a useEffect depends on the current value of a ref, but linter says that's a no-no. 2 useCallbacks can be co-dependent, but there's no way to write that co-dependence in the dependency list. Sometimes I want to return null before a hook. The clunky alternative for all these is a bunch of eslint-ignore comments.
> useState with an array with more than 1 component setting the state...
It sounds like you might be setting state by modifying the array and then calling the setter. This won't work:
array.push(thing)
setArray(array)
Instead, you have to update the array immutably, like setArray([...array, thing]).
> useCallback for event listeners
useEffect is usually the right place to set up event listeners (and has a built-in way of cleaning them up, by returning a cleanup function).
> Sometimes I want to return null before a hook.
Hooks pretty much have to be at the very top of the component, and eslint-ignore'ing those errors will probably cause weird issues later. Better to think about another way to solve the problem that doesn't involve returning early.
An issue I ran into recently: I had a modal Edit dialog with some form state that was initialized with the current values of the thing I wanted to edit. If that modal was always mounted and merely shown/hidden (<Modal isOpen={itemToEdit}/>), the state would be initialized once and wouldn't update when I changed the item-to-be-edited. The fix was to unmount the dialog, and only mount it when there was an item to edit, {itemToEdit && <Modal isOpen={true}/>}
One major point of the author is complaining that the dependency array in an effect is only compared by reference for objects and arrays.
To me this is a feature rather than a shortcomming: I use immutable data structures all the time (with array and object spread operators it is really easy to do so) and the effect will only update when it really needs to - thus only triggering a re-render (or rather reconciliation) when something has actually changed. This is in contrast to .setState() which triggered reconciliation regardless if a value actually changed.
The example in the article can be fixed very easily and doesn't require immutable data structures. If you're fetching something from a url and you don't want to fetch twice from the same url, make the dependency the url itself.
More generally, one should be destructuring object properties (since he later did that anyways) at the top of the function. It's no more typing, but isolates property usage to one spot. Then pass them into the dependency array.
Seems very convoluted to do that inside the useEffect, just to show how "it doesn't work".
Especially because often a re-run of the effect will be desirable when some of the props change, not always the whole bag..
If you think of them as mostly just syntactic sugar for closure state variables wrapped up in the module pattern, it's not so bad. (This is how some of us have been writing D3.js code for years!)
Long time React user here, have started experimenting with hooks. My general sense is the useState hook is a pretty neat idea, but useEffect and friends need some work. Definitely doesn't have that same "completely makes sense, this is a great API" feel that I typically associate with React.
The most fascinating thing about React Hooks is the perceived false dichotomy of hooks vs old class API. The old class API was pretty bad and could have been improved without changing the paradigm. Take componentDidMount and cdUpdate: they could have been replaced by this.onMountOrUpdate(fn) : that would have both solved the ergonomic issues and allowed multiple event listeners to be set up by multiple custom 'component enhancers' (the equivalent of custom hooks).
The market is ripe for a React like framework that keeps JSX and first class components but goes away with hooks in favour of a couple of event listeners and stuff like this.getContext(ContextName). Maybe also add MobX to easily solve state management. Vue is pretty close except JSX / first class components are an afterthought.
I think the part that is hard to grasp is that React essentially has it's own runtime tangled with JS. With the introduction of hooks, there are non JS things going on, which is why React relies on the quirk of having the same order and number of hooks in your component. It also has Suspense which allows you to exit from the render function and return to that spot, which is definitely not a feature of JS. So yea, it's weird. I think the interesting question is trying to figure out what this means for the next version of React. I would like to see if it takes a page or two out of Svelte's book.
There are no "non-js things" going on. I'll grant that they're doing some out of the ordinary things in order to support hooks and suspense, but it's still just JavaScript.
Edit: If you want to see how hooks are implemented without needing to understand the React codebase, Kyle Simpson has a project that provides hooks for non-React functions, the implementation is all in one file: https://github.com/getify/TNG-Hooks/blob/master/src/tng-hook...
Great post. I've also been ranty about hooks lately but am admittedly also just learning more ins/outs of things as I go.
That disclaimer aside, my biggest gripe is that I think hooks _could_ have been done in a "works from both OO and FP components" manner, so that all of these new (legitimately) "amazing to use b/c they're not HOC" APIs like the apollo hooks/etc. could be used by both types of components.
Instead, because hooks are they only non-terrible way of using libraries (again say apollo), users/codebases are basically forced to convert their OO components over to FP.
Specifically, to me the primary innovation of hooks is not "reinventing OO back into FP", it's "the ability for reusable code to attach itself to the component lifecycle".
I.e. a hook can know when componentDidMount/componentDidUpdate/etc. happened, and do the right thing. That's the primary innovation of hooks, IMO.
That would be extremely useful for OO components too, and also very doable, like just expose a component.addComponentDidMountCallback(...) type methods (or component.addEffect(...) or frankly even useEffect could do this b/c React implicitly know which component is being invoked right now).
With something like this, I think all of the "currently-FP-only" hook libraries could have "OO-based" equivalents, that are just as pleasant to use, and users could choose which paradigm they preferred on their own.
BUT, the biggest annoyance is that libraries would have to maintain both "FP hook" and "OO hook" versions, b/c current/FP-only hooks API didn't consider OO components a constituent in the design process.
(Specifically the only deal-breaker/breaking-API-change to use FP hooks from OO (if React wanted to allow this) AFAICT is that useState returns a tuple of [value, setter] instead of a State interface with getter/setter methods. If it returned a State getter/setter, then the useState could be invoked outside of the OO render() method, but then inside of render state.get() would return the current value. AFAICT all of the other hook APIs are already "OO-compatible".)
I view the draw away from OO as a feature and not a bug. Classical OO patterns should have never been introduced into JavaScript, and it had a just fine object model, that no one ever took the time to understand properly, for those small use cases it was called for. Emphasizing the functional aspects of JavaScript, it's clear strength, is the right call.
It just seemed like it was thrown in there because Java and C# developers who were increasingly being forced to use JavaScript begrudgingly just couldn't live without their "class" keywords, so they took to the forums to make JavaScript more like a "real" language.
> Instead, because hooks are they only non-terrible way of using libraries (again say apollo), users/codebases are basically forced to convert their OO components over to FP.
I know what you mean, but in practice it’s easy to retrofit a hook into a legacy class component by wrapping the hook in a tiny functional component. Sure, you have to find some other mechanism (e.g. make it a HOC, or use render props, etc) to make that wrapper component compose nicely with your class component, but that’s the price of not porting the whole thing over to hooks.
I generally like writing hooks. They're fairly straightforward to understand (until they aren't, but that hasn't bitten me often yet). However, reading other people's hooks is a lot more mental overhead than the same class methods, IMO. Either you have strict coding standards around them (which are pretty hard to implement with automated tools, so hopefully your code review process never lets anythign slip through) or comprehending someone else's code takes longer. For that reason, I've stuck with classes for the time being.
This seems like a fair point about other people's custom hooks. An important situation where naming helps so people can at least see what they do, without worrying about documentation
I love hooks. They've simplified a lot of components with state that don't "need" to be classes.
I have run into a sort of useEffect hell at times that makes me wish for the old life-cycle components as visually I find them easier to comprehend in very "active" and more complex components. But honestly I suspect that is because I don't quite understand useeffect well enough.
The TypeScript boilerplate that hooks eliminate for a react redux app is just insane. I don't think I can ever go back. For the slight bit of magic that make hooks work, you end up with such a concise function that describes the important parts and not the boilerplate.
They're not perfect. To each their own opinion. But they are what ES6 did for me to the language landscape.
I just wrote a tutorial that shows how to use Redux Starter Kit, TypeScript, thunks, and React-Redux hooks together. Should hopefully be a big help, for the reasons you described:
When Hooks are not properly understood, blog posts like this one make hooks look bad. However, that is just an error from the author, which results into unnecessary and overly complicated "solutions".
Yes, Hooks take a bit of getting used to and until you get them they will be ... weird. I did a few mistakes trying hooks until I actually "got" them (the fact that I tried to use hooks without actually understanding them certainly didn't help). But learning to do them right was certainly not harder than learning the quirks of classes.
In the beginning I thought they complicate the code unnecessarily.
Until I realized they can just be extracted into custom hooks.
The real power of hooks, besides the fact they are declarative!, is composition and by means of composition they can be abstracted away as custom hooks.
React is all about declarative UI composition but lacked the “primitive” for having stateful logic composition in an elegant and declarative way.
Hooks are great! I can sympathise with those suffering from JS fatigue, but I don't think they are merely a prettier/different syntax for doing the same thing, they are a significant conceptual simplification. This:
is not only shorter than the corresponding slew of lifecycle method implementations that would be required in a class component, it's also much more direct and readable. Trying to recover the intent of code by reading the implementation of various imperative methods is painful and error-prone, whereas it's hard to imagine how the above could any more effectively communicate the intent - every single token is meaningful.
From your complaint of deep comparisons in hooks, you should not be doing deep comparisons though which is the point of immutability in checking refs which is the whole craze of redux but craze meaning warranted in this case. In plain classes with redux, you usually check against a simple property or ref change anyway.
Please check out our new Redux Starter Kit package [0]. It includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once
The React-Redux hooks API [1] also requires less code that `connect`.
I just wrote a tutorial for RSK that shows how to use it with TypeScript, thunks for async, and React-Redux hooks [2].
(note this is not directed at redux, react-redux, or the wonderful redux-starter-kit library. I'm also a huge, huge fan of how createSlice combines action constants, action creators, and reducer handlers, all in one.)
Those react-redux hooks just exemplify the issues I have with hooks.
Yeah, potentially less code, but with more (and in some instances, completely unheard of) gotchas, more that the developer has to do to keep parity with not using hooks, and certain things dropped entirely.
I'm super interested in this discussion. I've been using React for years, and it's been smooth sailing. But somethings up with hooks, and I haven't been able to put it into words.
FWIW, the "stale props/zombie child" issues described in the React-Redux hooks docs [0] really have nothing to do with the hooks themselves. It's a combination of:
- The long-standing problem of trying to synchronize an external synchronous state container with React's async rendering cycle
- That our existing solution for this problem requires overriding values in context for connected components
- The fact that context usage _require_ rendering a <Context.Provider> to update a value
- The fact that hooks themselves do not do any rendering
So, the solution we have for avoiding stale props only works with `connect`. It's not that the hooks themselves are problematic or have inherent gotchas, it's just that hooks don't offer the specific additional capability we would need to implement that same solution in both places.
A user just put together a _fantastic_ article that dives deeper into this specific problem, and recaps how each version of React-Redux tried to solve it [1]. Highly recommended reading if you have some time.
I've completely stopped using class components after spending some time with Hooks in a small-to-mid-sized application.
The big wins for me were:
- passing an empty array as the last argument of useEffect makes useEffect work in a similar fashion to the old ComponentDidMount. You can have any number of useEffect invocations in a component, and for a component that is, say, loading data from two different sources, I find having two discrete useEffects loading just what they need much more clean/intentional than putting everything into a single lifecycle method.
- using Hooks with Context almost completely fills the giant state management hole that has been (imo) hampering React since its inception. Hooks + Context is a cleaner and more comprehensible solution than React + Redux for any application that has a reasonable amount of state complexity. For data-heavy applications, Redux may still be a good choice, but for everything else, the Hooks + Context combination is hard to beat.
How do you handle the drawback that changing the context will rerender all components that use the context? Putting too much state into a single context does seem like it could cause performance issues, especially with state that changes often.
I agree that if you had a large (in the dozens, if not hundreds) number of dependent components, it could be an issue. However, I've noticed no performance degradation with my usage -- I use a top-level AuthContext component to handle user data/login state, and another for access/caching of the app's main data.
In a single view (for lack of a better term), I'd guess the largest number of components that access either context is six.
Can you elaborate a bit about Context? I've been curious but wrapping multiple context layers to get multiple values feels awkward and wrapping an object to get CD around that feels like it screws over React rerendering logic.
Sure. The app I'd been working on had previously been using the original 'render prop' style of accessing context in functional components and using the this.contextType accessor for class-based components. Both approaches had their downsides:
- 'render props' are fairly awkward to write, and add a lot of noise to a component that's working with data from multiple contexts.
- this.contextType can only give a component access to one context. This is a pretty big problem.
With Hooks, useContext lets you reference and destructure contexts in a really elegant way. Say you want to show a user's name if they're logged in, with the render prop approach it's something like:
I find class components easier to visually parse and understand because of the organization of lifecycle methods into separate functions. I do understand the benefit of hooks, but I still reach for class components when working on something new because I'm extremely productive with them.
I agree with you that the class components are bit easier to visualize the flow than hooks.
At first, React hooks look simple and easy. But when i started thinking about setTimeout's, API calls, Re-renders, the flow get's difficult to imagine.
I switched over to hooks completely. Its just way faster to write and useContext, useState, useReducer, etc are great easy ways to structure my app. Working with useRef took some getting used to but now that I am used to it I would not go back
I haven't tried using them yet but hey do look like they remove a lot of boilerplate code from component. Kind of what redux did to state when we had to manually manage state in components.
I've started to think in hooks which is good. Not that it is always clear, but seen as a way to connect function and presentation, hooks work well. At times I question the directionality of hooks - do they hook function to presentation, presentation to function or both? The useEffect hook appears to be a case of presentation => function whereas many others are the opposite, and that can make it more problematic, as others have pointed out.
I'm not sure this a valid understanding, but it works for me as a heuristic.
I am starting to use hooks and it is actually way better to do async thunk axios http calls than to put it in a lifecycle method. the code is more succinct and clear and you don't have to keep the lifecycle in mind as much
Not to sound like a jerk but I cannot help but think this dude is whining about very petty issues. I read the article and found it to contain very weak arguments.
I totally can relate to the frustration when learning new stuff, especially when you have done things one way for years. Personally, the first month or so that I used hooks in react, it wasn’t pleasant, mostly due to me being uncomfortable with the additional load of learning this new convention.
And yes, hooks need to be grasped, they're quite something else. But once you get the hang of hooks, they're very simple to understand.
OP seems to be a bit stuck in a hole.
If you end up in a situation where your hooks come loose and the code becomes a mess — just delete them all and rethink your code / component logic structure. Believe me, it all can be simplified. Just rinse and repeat until you are satisfied with the outcome. That's when the "aha!" moment comes.
At the moment I only use `useEffect`, `useState`, `useRef` — the rest is unneeded so far even in my large-ish application.
P.S. sorry for the puns!