13 ms·
I have not been a fan of hooks thus far, but these are some intriguing examples. Especially the useMemo ones. My complaints about hooks are that with hooks we
by oaxacaoaxaca 7y ago
I have not been a fan of hooks thus far, but these are some intriguing examples. Especially the useMemo ones.
My complaints about hooks are that with hooks we lose useful tools in our react toolkit: class methods, separate componentDidMount/componentDidUpdate lifecycle events, shouldComponentUpdate, PureComponent, etc.
And my biggest complaint is that we're losing simplicity. React spread like wildfire when it first came out because it was so beautifully simple: a class, with something called `props`, and this.state and this.setState(). That's it. You can build SO MUCH with just that.
To me, with hooks, React's simplicity is being replaced by cloudiness. There's so much "magic" now. The order of hooks in the code is the most important thing for a hook component to work right. New linter rules had to be built to remind people where hooks need to be written.
I do appreciate the work that's been put into hooks. And like any other open source project, I'm incredibly grateful to the authors and maintainers. I guess I just wonder if anyone shares the same sentiments as mine, because it seems like the only thing I read about this topic is that classes suck, class components are full of pitfalls, etc.
- lilactown 7y agoYou use the word "simple," but it sounds like you mean something more like "familiar." That's not a knock; familiarity is important when designing an API that thousands of developers are going to use every day. But as you can see by the blog post, there are tradeoffs between doing something the way people are familiar with, versus a different way that's less familiar, but has different capabilities that might fit the problems better. Classes map to certain use cases very well; for instance, there's no Hook for "componentDidCatch" or any other error handling. That being said, you are not losing anything. The tools are simply different (although the old tools are still there, too!). As the blog post goes into, you can still implement the same _behavior_ as you did with componentDidMount/componentDidUpdate, but with Hooks you can formalize the different patterns you would find when using those methods into a custom hook that you can reuse. This wasn't possible before. shouldComponentUpdate can be easily replaced with React.memo, a higher-order component that takes a function that compares the previous props to the new props and returns whether they are the same. Wrapping it in React.memo without a custom comparator is the same as PureComponent.
- oaxacaoaxaca 7y ago> You use the word "simple," but it sounds like you mean something more like "familiar." Guilty as charged. I needed to hear that :)
- westoncb 7y agoTo play devil's advocate a bit: I don't think you were wrong the first time. I mean, as long as hooks are strictly an addition, it's easy to argue greater simplicity without. That's kind of trivial though, so I would ask how thematically related they are. If they both clearly stem from the same set of underlying principles and motivations, I think it's fair to say the addition is not substantially a complication. But is that the case for hooks? (I don't know—haven't looked into them yet :) But maybe the confusion is whether the additional complexity we're talking about is in React itself, or in the systems being built with react. In that case, old React may be simpler, but the code it produced was more complicated; or React with hooks may be more complicated, but the code you write with it is now simpler.
- Izkata 7y agoI'd argue it's more complicated by way of how much confusion I see with newbies. The old class-based lifecycle+methods was pretty straightforward - it only looked complicated at a glance. Hooks on the other hand do enough magic in the background that each one seems to come with its own set of misconceptions that have to be corrected.
- BigJono 7y agoThe ironic thing is that hooks are marketed by the react docs as being the solution to avoiding the "difficulty" newbs have with classes. Which I (as a proponent of hooks) think is fucking batshit insane, and clearly shows the disconnect of the React devs from their user base. Teaching beginners how to code, and how to use React, is a large part of my job. And I can say with absolute conviction that the React team is wrong about this. The 'hooks' whiteboarding session has gotten me far more blank stares than the 'classes and lifecycles' one ever has. Having said that, as a senior dev with a ton of React projects under my belt, I definitely prefer the hooks paradigm after using it in a few projects. It makes for cleaner code, and certain concepts are much easier to reason about with the hooks model. Just don't ever expect it to give you a cleaner on-boarding process. That's not at all where the benefit lies, despite what the docs tell you.
- mychael 7y agoAgree 100%
- axbytg 7y agoI would totally disagree. We started migrating our front end to React this year. We dipped our toes in with Class based components, found out about hooks, and never looked back. Hooks are confusing when you're trying to translate a class based component to a functional component with hooks. Starting a component or page from the ground up with hooks is so, so much simpler than class based components. As with any iteration of any framework, there are gotchas, but they are no worse than Class based react (totally opinion there). The mental model, once you latch on to it, is so simple.
- JMTQp8lwXL 7y agoYou don't have to use hooks and class declaration syntax is not going away. If you don't like a paradigm, you don't have to use it. I was quite skeptical of hooks at first, but after using them for a bit, they grew on me. It is not as simple as the class-based approach, but I also haven't found it to be a giant mental leap: my "warming up" to hooks took about a week.
- baddox 7y agoI’m genuinely wondering why the fact that the order of hooks needs to stay the same between successive invocations of your function component is considered a bad or confusing thing. The complaint comes up a lot, so I know that it bothers people, but I haven’t been able to understand why.
- ratww 7y agoI can sympathize with people that find it confusing, because it is an unusual requirement considering they're simple function calls. The fact that the only way to implement them (that I can think of) is messing with global state is also a very strange in today's functional-centric programming world. But on the other hand I agree that "keep the order!" is a very simple request for a feature that is so useful.
- erikpukinskis 7y ago> the only way to implement them (that I can think of) is messing with global state What do you mean?
- ratww 7y agoA regular global variable that is incremented each time you call useState/useEffect/etc and is resetted every time a new component starts rendering. This is why hook order must be maintained. I have no idea where this is implemented in React, but in Preact it's a simple integer that is incremented: https://github.com/preactjs/preact/blob/abc162c4d6442d5495f6aaba5d31c2bc75a3869b/hooks/src/index.js#L107 https://github.com/preactjs/preact/blob/abc162c4d6442d5495f6... I think there are other ways to implement them, such as treating the return of the render function as an AST and turning hooks into dumb factories, like a free monad. But considering the constraints they have (the need to maintain order) and the way it works (the fact the render function runs multiple times), I'm guessing it's just the same solution as Preact.
- brlewis 7y agoResetting it as render starts makes that number conceptually local to the vnode, even though preact's implementation involves a variable that's global in the context of that module.
- duxup 7y agoI love hooks for most things, but what I still struggle with are big higher level components that hosts a bunch of smaller components where I can see componentDidMount, update, etc.... and get a feel for the behavior overall. With my hooks I've not gotten that visibility but that's probabbly just me getting used to hooks and how to write them better in a way that makes things as clear as the old lifecycle methods. It doesn't help that I'm bit of a noob as far as web development goes and most examples I look at are pretty straight forward simple apps for teaching the concepts ... but my questions start when things start scaling up.
- k__ 7y agoFunny that you mention separate class methods for lifecycle stuff. Because hooks were created to solve the problem that this separation brought.
- fengb 7y agoLifecycles map closer to how the underlying UI layer works, but not to developer expectations. I've lost countless hours to desynced implementations such as failing to account for componentDidUpdate when overriding componentDidMount or missing a corresponding componentWillUnmount cleanup.
- stickfigure 7y agoMy initial impression was similar to yours, but after I started using hooks, my opinion changed 180 degrees. I've been slowly converting a rather large React frontend to hooks and it solves so many problems. We used to fetch data with HOCs. That never worked well with typescript and was a big problem when you wanted to use more than one (ie, fetching multiple data sources). With hooks we just add separate hooks for each data source we fetch... and the hooks themselves are much simpler than HOCs. Like the article mentions, hooks move up a layer of abstraction. I'm purging my brain of all the nitty gritty detail of React component lifecycles, because all I ever really cared about was getting data to update at the right time. And now that's the level I can operate at. Yeah, it doesn't look like standard object oriented code that we're used to. Maybe they could have chosen a different syntax, dunno. But after you get used to hooks, the grass definitely is greener.
- modwest 7y agoI'm there exactly with you. I could've written this comment myself. Love hooks so much. Excellent abstraction. edit: i guess i'm just supposed to upvote and move on but i'm enthusiastic about this topic :)
- acemarke 7y agoHooks definitely bring a different set of tradeoffs, especially compared to Higher Order Components. I'm a Redux maintainer, and the React-Redux API is a good example of this. The `useSelector` and `useDispatch` hooks are way easier to statically type than `connect` is, but calling those hooks embeds knowledge of Redux directly into the component instead of separating it. I specifically talked about these differences in my post "Thoughts on React Hooks, Redux, and Separation of Concerns" [0] and my ReactBoston 2019 talk on "Hooks, HOCs, and Tradeoffs" [1]. For myself personally, I've completely switched to writing function components as a standard practice, but we've also got a lot of devs who are newer to React and still writing classes. I don't feel a need to force rewriting all of those to function components, although I will probably find some time to let folks know that function components + hooks are a thing. [0] https://blog.isquaredsoftware.com/2019/07/blogged-answers-thoughts-on-hooks/ https://blog.isquaredsoftware.com/2019/07/blogged-answers-th... [1] https://blog.isquaredsoftware.com/2019/09/presentation-hooks-hocs-tradeoffs/ https://blog.isquaredsoftware.com/2019/09/presentation-hooks...
- city41 7y agoPureComponent is reproducible with React.memo(functionComponent) My main complaint with hooks is people seem to think they always need them. I've seem components where every single piece of data, no matter how trivial, is slapped into a useMemo(). It becomes a lot of mental overhead to trace through them all, especially confirming the useMemos have the correct dependencies specified. Especially when the vast majority of the time, useMemo just isn't needed.
- ratww 7y agoThat's a very common anti-pattern. :( I've seen a lot of cases in the wild where useMemo was used to wrap closures, "for performance reasons", but that's obviously more wasteful than just using the closure by itself. I think a lot of people don't realize that.
- RussianCow 7y agoTo be fair, if you're passing the closure to a component that extends PureComponent or is wrapped by React.memo, then memoizing the closure will ensure you take advantage of that optimization in the child; otherwise, you're passing a branch new function reference on every render, which means the child is needlessly re-rendering. At work, we have many components that are very complex but take a limited set of props, so these kinds of optimizations are absolutely vital for good performance. That said, you're correct in that this should be the exception and not the rule. It's pointless to do this for a component that returns a single element, as the virtual DOM reconciliation will be much faster than the memoization + props comparison.
- erikpukinskis 7y agoIf you have limited props, why would your component be rendering often?
- RussianCow 7y agoBecause the parent might render often.
- ratww 7y agoSeparate methods for lifecycle events is probably the biggest weakness of class components. They work well for smaller components, but when you have multiple things going on in a single class you have code related to a single concern scattered around multiple methods, and you start having to use conditionals inside your lifecycle methods. The article doesn't show pathological cases using classes, but with hooks it becomes possible to extract complex behaviors involving different lifecycles much more cleanly. I recently migrated a large React project to use hooks and a lot of repetitive code with lots of repeated patterns (especially things related to data access) is now much more cleaner. Now I can just have something like `useFetch` instead of ten lines of code that I needed just because I was using lifecycle methods. And the fact that the order of hooks matter is what makes them so easy to abstract. Having them store their own "id" would remove the need for that rule, but then you'd be running at other kinds of bugs.
- atombender 7y agoI have the opposite view. The things you mentioned (componentDidMount, etc.) are actually hard to get right, and class-based components don't help by scattering the logic all over the place. Functional components force your component lifecycle into a funnel where the lifecycle becomes obvious, because all the inter-dependencies are already there. For componentDidMount, you can replicate this easily with hooks: const Hello = () => { const mounted = useRef(false) useEffect(() => { if (!mounted.current) { mounted.current = true console.log("Mounted!"); } }, []) return <>Hello</> } Extracting anything into a hook is trivial: function useMounted(f) { const mounted = useRef(false) useEffect(() => { if (!mounted.current) { mounted.current = true f() } }, []) } const Hello = () => { useMounted(() => { console.log("Mounted!"); }) return <>Hello</> } Implementing an unmount callback is also trivial. The useMounted() example, coincidentally, shows a real benefit to hooks: Easy extraction of reusable logic. Hooks are like plugins this way. For example, one hook I've written that I use all the time is this [1], it lets you get a callback every time your component resizes. If you were to do this with a class-based component, you'd have to use an ugly HOC, which results in lots of unnecessary wrapping/nesting. For componentDidUpdate, useMemo() is arguably easier to deal with. [1] https://gist.github.com/atombender/b95bbd2014f6a3ec303b843a9bfa0db9 https://gist.github.com/atombender/b95bbd2014f6a3ec303b843a9...
- ArchReaper 7y agoUnless I'm missing something, you don't need all that just to have 'componentDidMount' eg: export const useMounted = (f) => { useEffect(f, []); } Edit: originally: export const useMounted = (f) => { useEffect(() => { f(); }, []); }
- acemarke 7y agoAnd to code-golf it a bit further, it can just be: useEffect(f, [])
- atombender 7y agoAh, of course. The use of dependency lists in React is something I keep forgetting exist. Thanks.
- ArchReaper 7y agoAfter using hooks for a while, I have to disagree with most of your points. The issue is not clarity or simplicity, it's familiarity. Once you start actually using hooks, things make a ton of sense. It's a different way to think about components, not an inherently more-complex way. The order of hooks is only important insofar as they do not change between rendering. That's it. Hooks should always be called, and always in the same order. A basic React Hooks component has way less boilerplate involved than a class component does, and hooks allow for much better code architecture when it comes to separation of concerns.
- deviantfero 7y agoI've just used hooks for the first time, I'd been working with react for three years before this, but let me tell you, using graphql/apollo with hooks is such a simple thing, I can't go back to using class components, it's so much simpler like this, loadables are a great concept.