React Hooks are a fucking stupid idea and always were.
They're basically just adding dynamic scoping to a language and framework which doesn't need it, in one of the most 'magical' and confusing ways possible. You have to care about execution order to understand exactly how they'll all work and that will bite you eventually if you're building anything without knowledge of all the contexts in which it's called.
There's a reason that most languages stick to lexical scoping: you can see the dependencies, in the same file.
And a large portion of the value of functional languages is that they avoid state, making magic-at-a-distance impossible.
Boilerplate is not the problem. Magic is the problem.
The 'magic' involved in hooks is a tradeoff; there are real benefits in the way you can consolidate logic, or mix in behaviors. Personally, I strongly prefer hooks to HOCs.
Many technologies have magical behaviors and are still very popular and useful (Rails comes to mind). I'm really liking the pros and cons being brought up in the rest of this thread.
To me this is the most visible win. useSelector for Redux, useIntl for react-intl, useHistory for react-router, useStyles for material-ui, etc. Almost every library I use radically simplified their API by adopting hooks.
I've really enjoyed working with React but it seems to me like some of the newer frameworks like Svelte have taken the best ideas from React without the baggage.
I wouldn't say it's harder, but it's certainly not simple. There are a handful of mistakes that I see repeated, but if you get over those hurdles, you can significantly simplify your components 99% of the time. It was very easy to have huge componentDidMount and componentDidUpdate methods in class components, and with logic scatter shot across a big file without the ability to easily reuse bits of it.
not sure what's 'fucking stupid' about that.
it might be harder to grok at first - but that's the reality of tools - by nature, they get more complex but more elegant, i think it's fucking stupid to want to go back to componentDidMount()componentDidUpdate, componentWillReceiveProps, componentWillUnmount, getDerivedStateFromProps and UNSAFE_componentWillUpdate. like... really?
The above criticism that you don’t get to have pure functional components anymore doesn’t really make sense to me - either you have some lingering state to deal with, or you write a pure function. Your hand is forced by the problem. You could switch over to class components but they’re really not much clearer to read.
Most of the bugs I’ve seen have been around JavaScript’s crummy equality checks and the need for more memoisation.
Smaller function components and then adding state with `useState` has simplified my code.
I can see how that can work for simple cases. Nesting components is going to get tricky though if the classes don't operate exactly the way the hooks expect.
Of course that's the problem: someone built hooks for their trivial cases and now they're the 'preferred' approach...
Edit: To clarify, 'simple' is going to be context-dependent since hook behaviour is. If your 'driving skeleton' of hook-based components is in the direct uninterrupted ancestry chain of every class component, you're probably using hooks in a near-ideal case.
The problem with the JS class representation is that people already understand what classes and instances are, and that leads to incorrect inferences about how React is working. In addition to better-organized code, the hooks abstraction is partly aimed at preventing people from making those wrong inferences. This also explains why they are uncomfortable compared to classes and functions — the point is that was a false comfort because those representations are misleading.
Dan Abramov calls hooks a "missing primitive":
"Why are these models insufficient to describe React? “Pure function” model doesn’t describe local state which is an essential React feature. “Class” model doesn’t explain pure-ish render, disawoving inheritance, lack of direct instantiation, and “receiving” props.
What is a component? Why do you start writing it one way and then have to convert into another way? Why is it “like A but also like B”? Because it’s neither. It’s a thing of its own. A stateful function with effects. Your language just doesn’t have a primitive to express it.
That’s what Hooks are. Those missing primitives. They are library-level but conceptually they are part of the “React language”. Hence the language-like “rules”.
They could be syntax. They would be in Eff or Koka. But the benefits are not worth the friction it creates in JS."
https://twitter.com/dan_abramov/status/1093696560280596491
I've said for a while, for example, that throwing promises for Suspense is using up "escape hatches" in JS. The rule of hooks is another one of those. Eventually, the React team will run out of escape hatches to implement "React language" semantics around the real JS semantics, and I suspect at that point sebmarkbage will move on to create a new view framework (as has been the case w/ e.g. sebmack and babel/rome, Rich Harris and ractive/svelte, etc).
It'll be interesting to see if whoever steps up to maintain React at that point will be able to grok its internal complexity, and to see how the community reacts to a rift when their favorite view library team pushes for one vision but the moved-on "rockstar facebook engineer" pushes for a different vision.
EDIT: fixed name confusion (thanks, swyx!)
just a minor correction, you probably mean seb markbage, who works on React, not seb mackenzie, who made Babel and now Rome and i dont think was ever on the React team.
i agree that when seb markbage leaves, it will be a big test of React's legacy. I've called it the "Ship of Theseus" moment for React.
Well, it was prototyped in standard ml first[1], wasn't it? - then ported/re-implemented (shoehorned ;) into plain js.
So some things that sml has, and made sense in sml, had to become part of the library/language/framework that is react?
Later came reasonml (a ocaml dialect) which is a lot closer to sml than js - and I think the state handling reflects that, like the readme for reasonml variant of redux:
https://github.com/reasonml-community/reductive/blob/master/...
"The code behind Reductive is incredibly simple. The first 40 lines include the entire re-implementation of redux. The next ~40 lines are a re-implementation of the react-redux library (without the higher-order component connect style implementation)."
In a sense, react has always been a design pattern - and a library to support/enable that pattern in Javascript.
[1] https://www.reactiflux.com/transcripts/jordan-walke#come-ide...
I understand "just a view library" might have been used to contrast it to full framworks that dictate a lot more than React, but it's important to note that the key React feature compared to other view libraries is precisely that it's not "just a view library": state is at its core.
It's hard to disagree with the the pain of React having to leave the comfort of plain idiomatic JS to better fulfill its goal, but to me React's efforts are in a way an experiment to find some primitives that should be baked into JS engines to allow for these mature, fine tuned experiences without putting the burden on the library.
Kind of what is going on with Node / Deno... and like the chap who quit the Angular 2 team to start Aurelia hoped would happen to him (sorry buddy!). My guess is that he'll find out that there is more to a framework than rockstar developers. Like Facebook backing, or like UX designers being in love with your library because it reflects their approach to problem solving. Like CRA, hot module reloading and all that jazz. These are all things that put React where it is today
I feel like that "appeal" was part of the marketing but the designers always wanted to create a new language. I switched a fairly large webapp from Angular to React pretty early on and I remember thinking that Flux (the design) was designed by someone that wished they were programming in OCaml instead. The whole "constants.js" for actions felt like a kind of defeat that they couldn't have unions and pattern matching in JS.
The same goes for hooks. People already understand what functions and js scope are and that leads to incorrect inferences about how hooks work.
Even more severe, newcomers who learn hooks while learning JS at the same time will get deformed perspective on how functions and scope work in JS outside of React world.
I wonder what about generator functions?
However, I'm disappointed.
In reverse order:
> 5. They Complicate Control Flow
A set of contrived examples, within which the only genuinely confusing part is not related to React at all. It's Javascript's object non-equality ({ multiplier: 5 } !== { multiplier: 5 })
> 4. The Rules of Hooks Limit Your Design
This is an interesting section but seems to point to a pattern that would lead to serious issues given any library/paradigm. It's criticising the Rules for their inflexibility, but in this instance they're helping you by pointing out the pitfalls of your approach, and encouraging you to rethink it.
An alternative way of looking at it is that it's again (like in 5) bemoaning Javascript's object inequality which is prevening the memoization here.
The other 3 bullets are pretty silly "boo new things" issues that are common to migration to any new API.
I often see developers mix up classes and functional components with hooks in abominable ways, and every pitfall to hooks I can find just boils down to improperly brackish OO class model polluted thinking.
The article would be completely contentless if not for pointing out the genuine pain that is JS object equality (the issue is that this is a JS pain and not a React pain: hooks just makes it more apparent).
The only thing I'll say is that battling with this pain has tended toward my inventing less generalised but more readable/maintainable/elegant solutions to most individual problems where I've encountered it. e.g.:
- object equality would solve this problem easily :(
- maybe I should've enforced strict immutables throughout?
- oh maybe I could approach it differently. Yes, let's try solution X
- hmm solution X isn't very reusable but it sure is clear and intuitive to read
https://immerjs.github.io/immer/docs/introduction
Once all state/object changes are being updated this way, a simple === can check for object equality. It's so useful, it makes me wish this was baked into the language itself.
If by "function as children" you're referring to render props, personally I was really happy to see that short-lived fad die out. I don't think render props made things simpler.
Now if we can admit we never needed Sagas just to do some data fetching maybe we can burn that stalled-out old bandwagon, too :D
(Sagas are a powerful pattern, I'm sure someone here is about to reply about how they're making good use of them. But I'd bet 99% of people using the redux-sagas library could be doing something simpler).
I wrote about why I chose thunks as the default in our Redux Toolkit package:
https://blog.isquaredsoftware.com/2020/02/blogged-answers-wh...
and our Style Guide docs page specifically recommends using thunks as a default:
https://redux.js.org/style-guide/style-guide#use-thunks-for-...
Mostly like them, but still not sure the canonical way to write update logic comparing prevProps to current props and running something if it changed where there is more than one dependency though. Need to store prev value in state to compare, or best to ignore the exhaustive deps warning and only run when the prop in question changes? Swear I looked all over and couldn't find a good answer.
Edit: I think I missed the point. Hooks do make providing state to children simpler in a lot of cases. I just don't hate render props that much :)
Suppose you replace:
const [count, setCount] = useState(0);
with const [count, setCount] = this.useState("count", 0);
I'm just speculating idly, but if you use `this`, it's clear how the function knows it's component. If you pass an explicit key, I don't think you need the order to matter. And if the order doesn't matter, well, conditional logic ought to work normally, even if it's a bad idea?I have no idea if there's a compelling reason this wouldn't work, but if it would, it seems like it could take a lot of the magic and nonstandard behavior out of the API.
What about JSX? It’s very useful but it’s also an absolutely huge departure from vanilla JavaScript and hides a fair amount of complexity behind what your code is actually doing.
Doesn't it just convert to React.createElement? I wouldn't call it absolutely huge.
Are you saying that hooks are implemented in a way that needs a compiler (e.g. like CoffeeScript or TypeScript) for them to work? I've always assumed they were implemented using closures or a similar pattern.
We have two projects, one using class components and one using hooks, and working on the class components one is unexciting, straightforward, sometimes repetitious, but never confusing. When writing hooks on the other hand it's constant gotchas; can't do that here, didn't memoize this, can't call that within this, etc. fuzzing until it works as Reacts expects. And then the bloody thing still renders on every frame. Back to the drawing board to figure out the right magic incantation. Probably memoize a few more layers, ugh.
- difficult to reason about except for a few simple use cases. The developer experience is nice if what you're doing is basic. But if, for example, you're aiming for 100% code coverage, unit testing hooks is an absolute nightmare.
Now regarding reusability: I have been doing UI code for many many years and I have rarely felt that logic inside UI components need to be reusable. First move all business logic out of UI components into model-layer objects. This eliminates most of the need for reusable logic in components. Then decompose your mega-components into simpler components. This removes all remaining need for reusable logic. If you still have reusable logic inside your component -- this is very rare -- allow some duplication of code, this is better than creating monstrous code that nobody understands.
[1] https://overreacted.io/how-are-function-components-different...
Either you're weird, or you've been doing JS mostly in the modern era of fat arrow functions, and fat arrow functions have succeeded at reducing the confusion from nested functions each with their own `this`.
Hooks reveal two major things with React:
1) React developers did not understand the component paradigm that they originally went with. If they did, then they would understand how silly it is that components cannot reuse logic. This was an entire debate many years ago. Composition vs. inheritance. You don't need to throw out classes or objects to get reuse.
2) React developers do not understand functional programming. I could write an entire essay on this. But it should suffice to say, FUNCTIONS DO NOT HAVE STATE. What React hooks introduce has more in common with dynamic scoping, of older LISPs. It fundamentally breaks lexical scoping, which is incredibly important for understanding code flow. You have to be constantly aware of when it's safe to use certain variables and the implications of using variables in certain scopes now. In 2020!! This is INSANE.
> they do not understand
> This is insane
This sort of post that asserts that nobody understood or put delicate thought into something is just pompous and lacks intellectual curiosity.
At least respond to their rationale. In doing so, you’ll find that everything is just trade-offs.
btw Dan Abramov is great to follow on twitter. He often responds to criticism and clarifies React decisions and links to good blog posts. If you use twitter it’s a nice way to get polite, bite-sized wisdom about React and Javascript. At the least you’ll realize how much good thought goes into React.
As some sibling comments note, this is not a fair conclusion to draw. And not that it disproves your statement, but Reacts original creator Jordan Walke wrote the first React prototype in SML. Not understanding functional programming is not on the list of things I would ascribe to him. He's a smart guy.
On a slightly different note, I'd recommend anyone try out Reason. It's slowly maturing and can be a real joy, at least compared to JS/TS.
I'm familiar with dynamic scoping via Emacs Lisp. I have yet to encounter anything like it in React, and it'd be surprising in any case to encounter dynamic scope in Javascript, a language which does not even support it. The closest I can come to understanding what you must mean here is to think you're very confused about how React props work, but that doesn't seem likely either - I can hardly imagine someone having such an evidently strongly held opinion about something, and having that opinion turn out to be based on a fundamental misunderstanding of the subject.
Would you mind explaining in some more detail the issues you see with React functional components? You mention having an essay's worth of material, and while that's probably more than we need to support a discussion, perhaps you'd be so good as to boil it down to a few points with a little more substance to them than "React developers don't know what they're doing" and "this is insane".
Doesn't matter much, but just b.c. it's interesting: JavaScript actually does support limited dynamic scoping - `this` is scoped dynamically like in usual Lisps, and there's a with statement[0] that acts somewhat similar with `let` in Lisp.
[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
It's funny you say that: useState is the same model functional languages use to handle mutability.
The GP is referring to purely functional languages like Haskell, where functions don’t have state and are referentially transparent. In Haskell, useState would have to use a monad.
Racket (and Lisp in general) has mutable state so doesn’t guarantee referential transparency. You can definitely write pure functions, and that’s good style in many contexts, but it’s not required or enforced.
I personally agree with the GP, and assume “functional programming” to mean pure functional programming. It’s common to use an FP style in non-pure languages, but I think this is FP if and only if you completely avoid state.
That definition leads to the conclusion that hooks aren't functions, which seems fine honestly. I mean, why insist on using them as functions when they're clearly not? Their syntax is a bit unfortunate, as is the fact that you're not forced to declare them immediately at the start of your function, which looks like it would have cleared up many of the potential problems. Either way their use case is clear, using hooks seems to basically dynamically declare a state monad for that particular function (hence why I'd recommend doing this upfront).
I'm not a React programmer though, so take this with a grain of salt.
It may be that your component is too complicated. Components should only have UI code. First move business logic out of the component, into your model layer, and make it reusable there. This step will eliminate most of the need to reuse logic in components. If you still have logic inside your component that you want to reuse consider restructuring your component into multiple simpler components.
The reality is that a component in React is still a class with internal state. React hooks are merely using a reference to "this" behind the scenes to store state but hooks are the only way to access that state. Therefore React hooks are basically a small DSL that adds features like dynamic scoping which is why lots of people think that this isn't regular Javascript anymore.
The first point is just whining about not wanting to learn new things -- we've on-boarded many new people onto our team in this time, and hook-based code is the easiest to understand. It's the old class-based components that are hard, and the most experienced team members work there usually to rewrite them as functional components.
The second point is only sort of true. They can interact with class-based components in a parent-child relationship. That's enough to gradually migrate your application towards hooks: any time you want to make significant changes to a component, rewrite it.
The third point is not a problem in my experience. Yes, we have rewritten some of our internal libraries as a direct result of hooks being available, not because the old ones didn't work but because we now had the tools to create a _much better API_ and took advantage of it.
The fourth point makes no sense to me. If you need to use conditions like that do something different, e.g. put the different cases ("a" vs "b") in different child components and render the appropriate one. Any programming paradigm has rules around it, and this is no different.
My response to the fifth point is "don't depend on control flow". You should be robust to evaluation order so it doesn't matter the exact order that React executes your code. If you have a execution order dependency in your code it will be highly brittle.
Infinite loops and missing dependencies are/were issues with `componentDidUpdate`/`componentWillUpdate` and `componentDidMount` as well, though. On the plus side, you now have a linter which can both point out these errors and automatically fix them for you. I agree that the whole thing is a bit leaky and dumb though, but there's no way to fix that without introducing some sort of compilation/optimization step and afaik the React guys aren't really considering that at the moment.
>Weird, unexpected reference issues
Not sure I've run into this before. Do you have any examples?
>strange programming patterns, a team member having to write a terrifying novel
The first bit seems like personal preference or something, not sure what you're referring to as strange. The `useEffect` novel exists because a ton of people had built up their (incorrect) mental model of how React works with lifecycle methods and were making mistakes or running into unexpected behaviour because they assumed `useEffect` was the exact same thing as `componentDidMount`.
1) Needing an advanced understanding of closures. Not always, but sometimes. That "sometimes" is often unintuitive, requiring weird solutions like useRef. Good luck beginners.
2) Things like updating reducer state by using a spread object, which creates a new object which can then send a system haywire. Seems fine, and is mostly fine in most cases, but hey, oftentimes not fine, and why that's so is not always clear. So then there's memoization, and useCallback and all of these safety checks -- each with their own dependencies array that need to be checked. It's really too much tbh. There are lots of solutions out there that use proxies to check this stuff; React should have baked that into the core and completely removed that responsibility from the user unless they wanted to opt-in micromanage performance of their code.
It seems to me that React Hooks, like so many things in the JavaScript world, solve a problem I do not have. To this day, despite being a heavy user of React, I don't even fully know what they do. I've read the "Motivation" section of the React Hooks Intro, and it seems that I have none of the problems they describe: I can (and do) easily add stateful logic via Rum mixins, and that logic is reusable across components. Also thanks to Rum mixins, complex logic is understandable and does not get all thrown into a single 'componentDidMount' function. As to "Classes confuse both people and machines", I find it hard to relate to this problem, because I don't really see any classes. I work with components that have a render function and mixins, and if you don't use any mixins, a component looks just like a function.
This tends to be a recurring theme: every once in a while I read an article about JavaScript and React, and I can't even relate to the problems, because they do not exist in my world. Another good example is hints on how to optimize apps to avoid excessive re-rendering, something I get for free with ClojureScript and immutable data structures (you can always quickly tell if two data structures are equal and avoid rendering).
https://crank.js.org/blog/introducing-crank
Crank itself is interesting, but what's relevant here is the broader critique of React there.
With class components, my state/props are clearly defined within the constructor and/or PropTypes. This makes it easy to understand the overall architecture of a component. Functional components with Hooks don't have the same sort of structure and can be difficult to understand at a glance.
One of my gripes with Hooks is that listening for async state updates requires more code/complexity than w/classes. In a traditional class component, you can add a second, optional argument as a callback which is called when the state has updated:
setState({ myState: 'newValue' }, () => { this.doSomething(); });
With Hooks, that doesn't apply. The useState "set" function doesn't have a similar argument. setMyState('newState');
Instead, you need to use 'useEffect' with an optional argument: useEffect(() => { doSomething(); }, [myState]);
This leads to potentially having many "useEffects" scattered throughout the component.That said, this is just my experience with Hooks after a few months of working with them. It's entirely possible that I just haven't had enough experience with them yet.
If you use a callback on setState in order to listen for async state updates like
setState({ myState: 'newValue' }, () => { this.doSomething(); });
then a week later, when you add some different code calling setState({ myState: 'newValue' }) somewhere else without remembering to add the callback, your callback won't run! Callbacks kind of break the declarative/reactive model.What do you mean? PropTypes work just as well with functional components as they do with class components.
it's much simpler to wrap the set function and just call your other function afterward like this:
``` const handleChange = (value) => { setMyState(value); doSomething(); } ```
The rules do start to get really tricky though with complex use cases of `useEffect` and multiple levels of nested hooks, and implementation problems are often not easy to spot or debug.
Dan Abramov has written a lot about the philosophy of hooks[0] at his site overreacted[1], I'd love to see a 'retrospective' write-up from him or another React team member about what they think the success and failures of hooks have been so far and if there are any changes planned for the future!
[0]: https://overreacted.io/why-isnt-x-a-hook/, https://overreacted.io/algebraic-effects-for-the-rest-of-us/, https://overreacted.io/a-complete-guide-to-useeffect/
- Hooks require substantially less boilerplate than classes.
- Rendering optimization problems with hooks tend to take more time to identify and to fix.
There are other pros/cons, but these are the ones that affect my work most frequently.
The team is pushing a functional declarative pipe method of building UI applications where things are built using a straight-line series of composed functional transformations. Personally, I think supporting this method with the hooks model of handling side effects is an improvement over everything else that exists in "roll your own" UI library land. I find these style libraries more enjoyable to build with, more expressive, and better suited to building things where you need finer grain control than template style libraries like Vue, which provide a stronger degree of predictability and ease of immediate use.
That's the thing -- it's a balance. Hooks add a nicely considered and balanced degree of order to the otherwise intentionally uncontrolled-so-you-can-do-the-controlling programming model of React. React identifies as the advanced lego set with the smaller more numerous blocks that you can build the cooler stuff with, and as such will always have a certain threshold of complexity.
This wonderful functional declarative pipe method of building UI applications where things are built using a straight-line series of composed functional transformations can really suck in real world applications as he tries to demonstrate. Anyone building with hooks now can relate to hooks bringing disorder to the codebase.
Has your experience been different? How did you avoid the pitfalls mentioned?
2. Don't mix Components and Hooks.
3. Agreed, change is hard. It's also the only way to avoid stagnation. In the long term, change wins. Or else we'd be programming in JS1995.
4. Insufficient example. What is the business case for a memoized hook returning hooks? Perhaps there is a simpler design, can't comment.
5. There is no global control flow. There is only per function component control flow, which proceeds from top to bottom. Possibly preempted by a hook/hookfn execution, if my early learning curve is to be believed. Which shouldn't matter if one is thinking in terms of 'pure functions returning jsx', as preempted functions do not return, thus have no observable effect.
Tip: Only change hook state from event handlers, never from render function code.
That's the point of _frameworks_. It's very ironic to see this being said in defense of React, given that its original appeal was precisely the opposite stance (i.e. React was "only the v in mvc", in response to the notion that frameworks of the time were imposing).
The useEffect pretty much provides a direct replacement for componentDidMount/componentWillUnmount.
I'm still on the fence, but so far it seems to me that using hooks makes my intent clearer than using the various lifecycle methods.
I've found HOCs easy to combine and reason about if I name them carefully, and am still using them on personal projects. When people complain about HOCs not scaling well, are they primarily complaining about name collisions, or performance issues due to deeply nested components/lots of render calls?
Hooks it's like learning a new language pretty much, which is only useful for react. I'm using them because of lack of better things.
I have (a lot of) component code that will never be converted to hooks. Can I rely on you not to flake out and pull an Angular on me?
That’s exactly what I take hooks as a sign of. I read the papers and the code when they came out. I still don’t get why they exist except to provide churn to work on. A half-reimplementation of objects with a super weird syntax in a language that already has objects seems like misguided make-work on a project that’s already basically “done” except for the boring, non-flashy work of maintenance and subtler improvements.
https://overreacted.io/algebraic-effects-for-the-rest-of-us/
Now the two issues I have with hooks still nag me in the back of my head, but are easy to get over:
- `useCallback` still makes new function references that wouldn't happen in class-based components. as someone who starts out with `class MyComponent extends React.Purecomponent`, that bugged me.
- easily access old props after updating. I built my components with something like `useEffect`, where mounting was considered "changing props from null to _something_", and updating was like normal:
class MyComponent extends React.Component {
componentDidMount = () => {
this.handlePropsChange(null, null)
}
componentDidUpdate = (oldProps, oldState) => {
this.handlePropsChange(oldProps, oldState)
}
handlePropsChange = (oldProps, oldState) => {
if (didPropChange('userId', oldProps, this.props) {
// now we know that props.userId changed, but also have access to `oldProps.userId` in case there's any cleanup that needs to happen.
}
}
}
I know this is possible with functional components / hooks, but it was nice to get this stuff "for free".That's true of the hooks API specifically, but not true of the underlying abstraction. Hooks are (informally) algebraic effects - one of the coolest and most general abstractions for inspecting and manipulating a program's control flow [1, 2]. Algebraic effects are still somewhat niche and most programmers haven't encountered them in name or in practice, so in that regard, hooks are actually one of the fun cases when learning a new API is mind expanding.
[1] https://github.com/ocamllabs/ocaml-effects-tutorial#1-algebr...
[2] https://overreacted.io/algebraic-effects-for-the-rest-of-us/
> React, like socialism, perfectly solves the problems that it created.
One criticism of the article is that it seems to argue that you lose the ability to provide HOC (and probably render-prop) APIs if you adopt hooks in your library. But it's fairly easy to automatically turn those types of hooks into HOCs, so it actually makes sense to have the hooks API be the primary one. You can't really do it the other way around, i.e. turn a HOC into a hook.
I think 1, 2 and 3 aren't really great arguments. There's always more to learn, and it seems that class components are on the way out, and are around mostly due to backwards compatibility. But it is true that a lot of legacy code uses them. I wish they'd have started with functional components, but I can't blame the team for not figuring out all of the details in advance.
I'm curious what others think. Thanks!
A single component wrapped by 3-4 HoC that each do trivial tasks always felt like mental strain rather than a helpful abstraction. My favorite was HoC's that added class component functionality to function components... just use a class.
https://github.com/dominictarr/observable https://github.com/adamhaile/S
Mobx and Vue use the same technique for running computeds.
As does Solid and Sinuous, etc...
Don’t feel dirty for doing things simply. If your functional component has entire lookup maps for hooks, it’s probably too complicated as a standalone functional component to drop hooks in.
Hooks are like a screwdriver; great for simple stuff when you want to reduce code overhead.
Sometimes you need a power drill, though, and classes and the old-school lifecycle functions are wonderful for that.
Just as one example, in a lot of posts and commentary I've seen, is that hooks are replacements for both HoCs and render props.
Admittedly, I haven't yet tried to do any actual development with hooks, but I can't even figure out how to solve the problem in the example in docs for HoCs[0].
Do you pass in a hook as a prop? That doesn't seem wise. A custom hook for each data source still has the same code duplication.
The docs talk a lot about how to build individual components using hooks, but very little about tying them together.
- Giving function components the ability to have internal state and trigger side effects, giving them the same capabilities as class components have had
- Reusing logic across components
I talked about the progress from mixins to HOCs to render props to hooks in a talk at ReactBoston last year [0], which had an example of tracking mouse coordinates using all four approaches. In that sense, yes, they do replace the other techniques as a way to reuse logic.
You call them inside of your function components, like this:
function MyComponent() {
const [counter, setCounter] = useState(0);
const {x, y} = useMousePosition();
// rest of the rendering logic here
}
[0] https://blog.isquaredsoftware.com/2019/09/presentation-hooks...it's almost like software development is a highly skilled technical profession that takes years to master?
don’t tell him about SwiftUI
The main issue I have with hooks is that I can't easily trace why updates are being triggered in my app; this makes it hard to debug performance issues. For example, my app once got really slow, and the profiler told me that a root(ish)-level component was causing a lot of re-renders. Unfortunately, that component used multiple hooks, and the only way I was able to isolate the problem was by binary-searching, deleting hooks until the re-renders stopped.
Anyone have better ways of dealing with this?
For example, at t = 0, the output is one thing. When t = 1, the output is another. The same way of thinking can be applied to hooks. Some hooks only execute at t = 0, and at that time, variables x, y, and z also have specific values.
Hopefully you can think this way and your values won't intertwine so much that it becomes hard to trace.
The biggest problem I have with Hooks is readability. IMO, functional components with hooks are harder to reason about since they obfuscate logic in a weird react-specific contract. Class components have a much simpler, contract and syntax. They also felt much more natural since they picked up on familiar concepts of JavaScript, albeit with a couple of drawbacks. I get the advantages of hooks, but in a way, at least to me it seems like they, at substantial cost, solve a problem which I barely ever encountered, even after building react apps for many years.
I'd strongly recommend reading through the React hooks docs, as well as the other hooks resources I have linked here:
https://github.com/markerikson/react-redux-links/blob/master...
The other criticisms seem a little bit like someone who doesn't understand how hooks works criticizing how hooks work because he doesn't understand how hooks work. Perhaps, he doesn't understand how hooks work because he doesn't want to learn more stuff?
function Component(props, { useState, useContext }) { ... }
Of course that would break backwards compatibility with the old 2nd argument being context, so I get why they did it.All I'm saying is that instead of hooks API being imported from React at global scope it could be provided as inputs into the components directly. They would still exist in the function body as you put it.
The last two, now, do look like they can turn into serious problems (and a lot of confusing code) if one isn't careful.
Then again, I write this as someone who mostly uses, and vastly prefers, Vue so it's not like I'm an authority on react.
For instance, if I have a single app and component, and use a hook, I understand that the hook and app have some sort of implicit connection.
But what happens when I have two distinct react apps on a page - does that break the ordering that hooks require? How does a hook have any affinity to the app, or does that even matter?
I'm sure looking at the code will cause a "oh, I get it" moment, but that doesn't mean it's obvious to anyone just picking up hooks.
Honestly, I think hooks are fine, but I'd almost prefer a signature like `const MyComponent = (props, { ...hooksHere })` so there's at least a false affinity between the application and the component.
In other words, this is not legal:
const [stateA, setStateA] = useState(false);
if(stateA) {
const [stateB, setStateB] = useState(42);
}
Call as many hooks as you want, in whatever sequence you want. Just make sure all the calls are at the top level of that function component, and that you don't somehow change the sequence of those from one render pass to the next.As for how they work, React already has metadata that describes each component in the tree. There's a specific field in those metadata objects that gets used for tracking internal component state, and for function components, that field stores an array / linked list of the hook calls you've made and their last saved results.
See these resources for more explanations on how hooks are implemented:
https://github.com/markerikson/react-redux-links/blob/master...
I mean, that's the whole point of hooks... they get the context of whatever host function scope they are in. That's why the 'reusable logic' spiel. So if you create a useLocalStorage hook, for example, you can then plug it into any function component and it will use. It's as if each function was an invisible class, with an invisible this.state
I guess I see a lot of this as evolutionary. It's unfortunate that there has been so much change, and the timing might not be great for some projects, but I would not prefer a world where I was still writing and using HOCs and class components.
In my day job I work on a pretty old (in React years) project, and we haven't had trouble writing new code in a functional + hooks style. Still plenty of class components abound.
First of all, there is stateful code and then there is effectful code. It sounds like you are talking about the second, not the first. You can have tons of state and remain mathematically pure.
Where people get hung up is effectful code, or mutable state. Even one of the more hardcore FP languages, Haskell, does not try to abstract that out. Instead it embraces it fully by giving constructs in the language to describe and control effects! This is far more powerful than straight up imperative languages. If anything, writing mutable, effectful code is more powerful in Haskell than in C/C++.
Where Haskell gets difficult is when writing effectful code that interacts with external C libraries and the OS. But this has nothing to do with purity, state, or effects. It really only has to do with the fact that it is designed to have lazy evaluation by default. Which itself has a lot of advantages, but it makes this interaction more difficult as code does not execute in the same order as you write it.
You may find that languages such as OCaml, which are fully functional and have strict evaluation are a joy to work with.
You could model the universe just as accurately as a stateless function of time as you could as a stateful entity that moves through time.