I am disappointed with the React team’s decision to push functional components and hooks as the standard way of working with React. Not sure if the reason is to make React more approachable to newcomers or not, but in my experience leveraging the power of the component lifecycle through class components and decorators is the most fool-proof way to build and maintain large applications. Particularly leveraging shouldCompomentUpdate for performance and componentDidMount/componentsWillUnmount for registering and disposing of component dependencies is very easy to reason about and scale.
The reason they introduced hooks was exactly that component lifecycle and decorators/higher order components were found not to scale well in larger codebases (as experienced by the people using React at Facebook).
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.
Can you provide links to articles where react devs detail the scaling issues?
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?
IMO, we've traded the complexity of `this` with the complexity of hooks. Maybe I'm weird, but I never really wrote JS that caused scoping issues, so I never found `this` to be a problem. At the very least it's a complexity that is internal to the language itself. Hooks just feel so weird and alien to JS. I find them very, very difficult to reason about.
- 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.
I'll see if I can find the link, but I recall seeing some references in the official docs, naming `this` complexity as a key inspiration for looking to hooks as an alternative to stateful classes.
But focusing in on the ‘this’ criticisms highlights the general criticisms. They think ‘this’, or the Class model is a barrier to entry for React.
React is a great framework, and super intuitive on so many fronts, but where it misses, it misses big. To come to the conclusion that the Class model, a pretty predictable pattern, is more a barrier to entry, or a conduit for confusion, is really misguided.
Just take a look how simple functional components can take on a quasi Class-like form with lookup maps for hooks in this particular article. When you advocate for stuff like this, you are injecting the community with effectively bad practices.
The React dev team can not resort to the ‘You probably don’t need ________’ article in perpetuity. At some point, they have to say to themselves - ‘We probably don’t need to add this to the API’.
I agree that the lookup map thing looks like very bad advice. I can't see a reason why you'd do something like that.
On topic, it has been my understanding that the React team is moving away from classes for a number of reasons. Not just because it confuses people (although the pattern seems to create expectations that aren't met due to Javascript's weird 'this' behavior), but also because it doesn't have a good pattern for code reuse (as demonstrated by the lack of mixins that used to be popular in React's createClass syntax) and also because it seems to be a suboptimal pattern for compilers.
That said, I've moved on from class components to function components with hooks and I can't remember the last time I thought something would be easier to implement as a class component, even though it is still an option to do so. But that is, of course, anecdotal.
Disagree. I understand `this`, but I very, very rarely encounter situations where it doesn't mean the same thing as Java's `this`. When you work on a full-React codebase, it just doesn't really happen. We used to have weird `this` behaviour on event handlers, but arrow functions and hooks fixed this.
`this.foo = this.foo.bind(this)` and then the new feature of method properties `foo = () => this` vs `foo() { this }` are constant sources of confusion for people who aren't well-established in Javascript/this. Not to mention function() vs arrow functions.
All exist because of the idiosyncrasies of `this` that you may be discounting because of your familiarity and already being over the learning curve that ensnares people daily.
But that took the simplicity away. Everyone that knows modern JavaScript knows classes. Now they have to learn this alien concept called "hooks".
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.
You're only calling it not simple because you're not familiar with it. I personally think hooks are simpler despite being different. Everyone knows "classes" but React's usage of them is not typical and there _are_ non-standard behaviors about using them that you have to learn when learning React (because the classes necessarily exist within the React runtime). Either way you're dealing with React.
And on a day-to-day basis for me, there are still plenty of uses for reusable logic inside of components even after extracting business logic / creating small focused components, especially as it pertains to presentation. A simple example that I use somewhat frequently is a window-size watcher.. a pretty simple hook that watches the window-size, re-renders when it changes (on a debounce), and it provides the current width x height of the window, allowing the component to use those values to calculate some view parameters. With hooks, it's as simple as plopping `const { windowHeight, windowWidth } = useWindowSize()`. Without hooks it generally requires wrapping a component with another.
I've been using hooks basically since they came out and IMO they're way more in tune with React's programming model than class-based components. Even if you create custom hooks for most components, the paradigm still encourages developers to encapsulate related pieces of component logic into their own hook-functions rather than spreading that logic across multiple lifecycle methods. I haven't come across a single situation where I would have preferred a class based component.
The problem I have with hooks is that I see cool examples like the window size watcher, but when it comes to my own code I have:
1. Some stuff that happens when the component gets mounted
2. So local state that gets maintained (shown error text, whatever, red x, whatever)
3. Some stuff that happens when the component is unmounted.
React classes fit 99% of my use cases. Yes HOCs to get navigation and stuff working is a pita. But honestly I'd have preferred a way to get the cool hook stuff added to class components instead of adding all the class component stuff to function components!
Exactly. I'll often use a shared useOnMount and useOnUnmount for additional clarity for those in our codebase unfamiliar with how useEffect works.. there's nothing preventing you from writing custom hooks to replicate the effect of lifecycle methods for the transition period.
For the simplest cases, class components might be clearer with its clearly labeled lifecycle methods. But most of the time, if I have multiple different things happening on mount, hooks are definitely simpler as you can divide / encapsulate based on feature functionality rather than lifecycle.
If you then extract those bits into custom hooks in order to name that piece of functionality (e.g. in that example, useTitle() and useWindowResize()), the clarity improves dramatically.
Mixin's where a bad idea as well, they where basically just multi-inheritance lite. In all honestly I have never seen a mixin accomplish something that could not be accomplished by single inheritance and wrapping a private instance that contains the functionality one want's from the mixin. Sure it's a little more boilerplate, but it's understandable, uncomplicated and none-magic boilerplate. I am not down on Hooks per-say but I am skeptical of magic and hidden effects caused by code outside of the visible flow of code. Sometimes the magic is awesome but sometimes it's just not worth the hassle.
I never had scoping issues around `this` in my react code (earlier js libraries, yes), but I still love hooks. I find them more comprehensible and consistent. I love that the API is smaller. I had to fix a number of bugs caused by the life cycle methods when I did a react 15 to 16 upgrade a while back, and it seems unlikely those can crop up with hooks.
A big problem with `this` is it is mutable. Dan Abramov has a nice article[1] explaining why that is a problem and how it leads to subtle bugs that are common in React apps. Hooks eliminate this problem, and I would guess this was one reason they decided to move forward with them.
That’s not a problem with “this”, but a self-inflicted problem from React’s chosen model of reusing class instances. It’s a design choice made by them and not a language issue at all.
> Maybe I'm weird, but I never really wrote JS that caused scoping issues, so I never found `this` to be a problem
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`.
I gotta be honest, I agree with this - `this` was something that bit you once, and you quickly learned to `.bind()` (or use the 3 line polyfill for it...).
I find my older pure JS codebases so, so much easier to go back and remember what was going on than some of the React stuff I've had to interact with in the past 5+ years - and I say this as someone who has written, released, and taught React.
The problem for me is, I think, I'm not sure you can do better than React. You'll either wind up with some complicated beast from hell (Angular) or veer off into performance-benchmark-overvaluing (Svelte).
The problem with 'this' (well, one concern): sometimes you don't have a choice. Suppose you consume a library, where you call a function, passing a function as an argument. Your function's this context can be overwritten, even if you don't desire it to be. I recently ran into this issue when consuming a third party library.
From what I've learned using them, hooks are a tool like anything else—pushing one method as "the" way means that you make a lot of poor engineering decisions.
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.
I don't think this is a great critique, simply because the pain I've encountered using them isn't mentioned here. The incompatibility with class components is what it is, they're different programming paradigms that you have to choose between. If your library leaned heavily into HOCs, that was an unfortunate choice and I'd recommend making a new library because HOCs were always unwieldy and had problems with composition. Nothing to do with functional components or hooks really, just a very heavy pattern that can typically be done better with another approach like render props.
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.
A lot of times I just use a simple React class. The author’s lookup map to return a lookup of other hooks, yikes. A class component would probably solve that in a more predictable way.
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.
Right, I'd be curious to know a lot more details of the real-life use case that inspired that. My gut feeling says that there's maybe a state machine of some sort which could possibly be split into a sequence or maybe hierarchy of smaller components, but it's hard to tell specifically what the alternative might be without more details on why they thought a lookup table might be useful.
The reaction to react hooks has been (as far as I've seen) a little too positive, so I was looking forward to read a genuine critique.
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've never* used hooks and I only got 1 question wrong in the author's Google Forms quiz (would've been 2 wrong, but bullet point 4 had already signposted the object equality gotcha).
So there doesn't seem to be a huge amount to learn in hooks as far as I can tell.
* I am an experienced React developer but haven't had much opportunity to work with it in the past 2 years.
TFA addresses this: it qualifies by saying learning is good as long as it's useful outside of whatever narrow scope they appear. The real criticism in that section is that hooks (and e.g. gotchas related to things like useEffect, stale closures, etc) are non-transferrable knowledge.
As others have said, I still don't really think it's applicable because learning hooks takes an hour or two, but to try and address that point: I don't really think the knowledge is non-transferable at all.
There's two aspects to learning hooks:
1. Learning the API. This is not a "conceptual" part of learning but rather "this function name does this thing". This is the same with learning any programming API and is almost always non-transferrable (with the minor exception of some open standards, except that varying implementations of them still tend to have quirks).
2. Learning the patterns and concepts around applying that API to problems. As far as I've seen just from TFA examples, they're very widely applicable. Memoization is widespread. Functional style is widespread. The most complex stuff handled by the quoted examples is maintaining state in nested hashtables, which is such a widespread concept that observable/immutable libraries like MobX et al & ImmutableJS et al have been written pretty much focused entirely on this problem space.
I initially had a few, but after making sure they weren't just learning curve exasperations in disguise, I realized they all boil down to that they should've just started out with this and never had any class components.
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.
It would be amazing if JS could have object equality in a performant way. I'm not sure if Python does anything interesting under-the-hood, but identical Python dicts have deep equality just fine. That would make hooks great in my opinion, where as right now they are just good. Dealing with object non-equality is like 90% of the friction I experience with hooks. Any pattern that requires me to reorganize my code (e.g. passing in individual parts of an object rather than just the whole object to the dep array) is an inelegant pattern, imo.
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
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.
I was skeptical towards hooks when it was first introduced. I was hesitant to use it. Then, I used it for a few components in my projects. I realized how much simpler my code looked, and migrated completely to hooks. No regrets.
Hooks are magic with new rules that are different from regular JavaScript. They don't follow the regular flow you'd expect it would. Requires devs to think a lot about hooks to make sure something is messing them up. Also needing eslint to make sure your code is ok, is a boy flakey.
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.
Once you understand that function components aren't simple, contained functions but rather components that exist in a parent scope (React) and that React actively manages them, it's not magic at all. Also, you don't need ESLint; the rules are pretty simple.
All of this reads akin to someone criticizing an apple for not being an orange. Every point is an intentional design decision. Learning new things is necessary, leaving class syntax behind was a choice, and imposing limits on (controlling) application design is the point of libraries.
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.
You didn’t actually counter any of the authors’ points.
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?
Well #2 isn't really a pitfall, for starters. I have a new feature that solved a problem I had but I can't use it in the code I wrote before I had it without refactoring? I don't think that critique really has anything to do with Hooks, just software development. It's also basically a rephrasing of #3.
1. It's not more stuff to learn, it's different stuff to learn, arguably less. Learning Components is not a prerequisite for learning Hooks. Or perhaps I missed the memo, as I've built an app using Hooks, and still haven't need to learn what 'componentWillMount' is supposed to do.
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.
> In the long term, change wins. Or else we'd be programming in JS1995.
Change for the sake of change is not a sound argument.
The thing about hooks is they don't enable a single thing we couldn't already do with HOCs. They are also much harder to read, because stateful logic is now just sprinkled around your render method rather than being isolated to places you know to look for it. I won't be using hooks ever, as far as I'm concerned.
He/she gave a very valid critique of state being jammed into your render method, vs state being handled in a predictable pattern in Class components.
And lastly, let’s not minimize change-cost. In the real world, it’s a cost. We’re all willing to pay it if it’s necessary, or affordable, but not because someone showed up and said ‘change please’.
> imposing limits on (controlling) application design is the point of libraries
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).
Hooks are great for simple use cases like `useState`, `useContext`, some uses of `useRef`, etc. and can make the code easier to read and reason about (while conveniently working very well with TypeScript).
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!
I have some similar gripes. I find Hooks to save a bit of coding overall. I've found my functional components to be about 10-20% smaller than my class components. I'm not 100% convinced it's really worth it, though.
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:
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.
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.
Components with lots of hooks in them remind me of spreadsheets. I find myself tracing from one hook dependency array to the next, trying to follow the logic.
I personally don't use hooks (or functional components) at all, but recently read this post from Dan Abramov about algebraic effects which makes a point (among others) the hook mechanism is a pretty simple way to implement state/effects/context in a language with algebraic effects.
Honestly? Maybe it is, maybe it isn't. Imagine replacing "algebraic effects" in your question with "exceptions" - theres no clear answer. What do you think?
My take away from hooks is that it is pushing toward making your components simpler. One of the gotchas of hooks is that it kind of "lies" in the way it looks. Take useRef or useState for example. These things are only defined one time even though the are declared in such a way to look like they are defined over and over again each render. They are actually key lookups under the hood. This was a main point of confusion for me initially and I'm sure I'll find out more that I assumed incorrectly as I go. Auto-magic sometimes is confusing to me.
They behave like class property & method declarations. But scattered about in a function and looked up by order rather than name. This is exciting and not considered redundant and obviously a bad idea, for some reason.
After using `ember` and the wonderful `ember_data` at $PREVIOUS_FIRM, I wholeheartedly agree. React is good for what it's good for, but the community sadly did not stop there.
I mean, I got full marks on the quiz in the article. I had to think about the code, but no more than if the same had been implemented as classes. I have been using React for a very long time though, but the areas where execution order can be confusing aren't a problem new to hooks.
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.
An important point I don't see being made in the article or the comments is that hooks are meant as a more faithful (or at least less misleading) representation of what was going on under the hood in React already.
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."
As a user of a library, I don't really care how it works. Under the hood it can be arbitrarily complex or simple, and please feel free to change the implementation weekly for all I care. I care very deeply about my own components, when they render, what causes them to re-render, and that I can control and reason about when they re-render. Also, stability of API (in number of years) is way more important than new whiz-bang features.
It's interesting that the original appeal of React was that it was "just a view library", but now apparently it's more like a "language". It really shows the biases of the maintainers (the "just a library" thing being a philosophy I liked from vjeux, and the "language-likeness" being very obviously a heavy influence from sebmarkbage). The thing w/ "language-ness" (as opposed to "library-ness") is that additions and changes to a language tend to become more and more difficult to make over time because semantics deeply affect everything in the system, whereas well designed APIs in a library-oriented approach can be well encapsulated.
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.
> and the "language-likeness" being very obviously a heavy influence from sebmack
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.
Was it sold as "just a view library" from official sources?
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.
> Was it sold as "just a view library" from official sources?
Yes, the "V in MVC" term came straight out its main page:
> JUST THE UI
> Lots of people use React as the V in MVC. Since React makes no assumptions about the rest of your technology stack, it's easy to try it out on a small feature in an existing project.
Thanks for finding that! Yes, it seems like this way of seeling it could cause false expectations. It is still true, though, that "React makes no assumptions about the rest of your technology stack, it's easy to try it out on a small feature in an existing project".
I don't see any contradiction - you still need controllers and models today. It's a front-end view library, so it manages state, but nothing else (unless you make your controllers and models a part of the view, which was possiblewith server side views just as well - remember PHP?)
> 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.
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
> It's interesting that the original appeal of React was that it was "just a view library", but now apparently it's more like a "language"
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:
"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.
Thank you for this latter link where the original author of React describes its beginning.
After reading it, I finally feel like I'm starting to understand where React came from, why it's designed the way it is.
The paradigm shift that React brought to JavaScript was to "bend the language" to implement concepts and design patterns from ML, a functional language with roots in Lisp, with static typing, algebraic data types, and foundation in lambda calculus and category theory.
When you joked that it was "shoehorned" into JS, I got an insight into the reason why some design decisions in React feel awkward and strangely non-idiomatic. It explains, in part, the strong emotional reactions seen in this discussion thread, a number of justified opinions, its problems as well as benefits.
I've been skeptical of the design of React Hooks, and still am, but now I'm interested in learning its influences, to understand the logic behind them. I wish that it had been implemented to be more "React-agnostic", like JSX, as generic extension to the JavaScript language.
You're very welcome. I do believe looking at reasonml and reasonreact is a good way to gain insight into reactjs. It's on my (so very long) to-do-list.
Ed: as an example, I found this (oldish) post on hooks in reasonreact:
Finally I came across this - I thought maybe a sibling comment mentioned it - but a quick search didn't turn up anything - but imo it's a pretty strong argument for hooks (in js react) :
Nice resources! I've bookmarked them for later study.
That last one I like, especially where the author recommends to "forget everything about lifecycle methods" and think of hooks in the context of synchronization. It makes sense, as a declarative way to describe state and state transitions.
The aspect that's unsettling is that they're not idempotent pure functions, but rather deeply tied in with how React works internally. They can't be used outside of React, and require the programmer to understand the magic that makes them stateful.
The common issues that beginner users of hooks encounter, like the "captured scope" of variables, or that hooks must be run in the same order every time, never conditionally - I suppose these are some reasons that make me (and other hook skeptics) react to them as "code smell". If someone had designed a library totally unrelated to React this way, I wouldn't want to use it.
Looking at ReasonML, it's quite elegant and intuitive how React Hooks fit in. In fact, the code examples look very similar to how I structure my React projects, with state and actions (instead of a reducer or Redux, they use immer for immutable state changes).
I'm staying open-minded about hooks, and I think the more I learn of its roots, the more I'll come around to accepting them as part of idiomatic React.
Right, I meant that Sebastian is the current maintainer. Not to detract from Dan Abramov etc, but the feel I get is that Sebastian is the one who really sets the pace for where React is going these days (i.e. hooks, suspense, etc). Correct me if I'm wrong.
Well, you're factually wrong. Abramov is the current React maintainer. Markbage is obviously a core team member but contributing good ideas doesn't automatically transform one into being the project maintainer.
Didn't mean it as an XOR. FWIW, looking at the github commit history one might get the impression that acdlite is the main committer in the repo these days, followed by trueadm and bvaughn (gaeron doesn't look all that active recently in comparison). I don't really hear those three core team members talk much about JS since their twitters tend to be less technical. My impressions come mostly from what I see the more technically-vocal react team members say publicly and I feel like gaeron often makes it sound like the bulk of react concepts are other people's contributions. Markbage certainly seems like the "vision" person in the team </shrug>
>It's interesting that the original appeal of React was that it was "just a view library", but now apparently it's more like a "language".
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.
I don't see how generators would change anything here. "with effects", "stateful", and the integration between the two are all equally important in the statement.
Well generators are a good primitive to represent "stateful" "functions" "with effects". The concept of hooks, which somehow give the control back to React to do something, map marvelously to react.
I'd be surprised if generators didn't come into React at one point or another
> 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.
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.
This is really compelling - thanks for linking it. Is there a downside here? Has React responded at all? I don’t hate hooks, but using async + generators like this looks so obviously better and more intuitive here; like such a clearly great, simple idea that I’m embarrassed I didn’t ever think of it myself.
It's unreal that in React I have to deal with occasional infinite loops now because of hooks. Sure, React catches the loop cycle so things don't totally freeze but I don't recall ever having to deal with this before them. Weird, unexpected reference issues, missing dependency accidents requiring linters to prevent, strange programming patterns, a team member having to write a terrifying novel like https://overreacted.io/a-complete-guide-to-useeffect/ for something that was never really a problem before. The list goes on and on.
The problem before was that your class component was not updating correctly and rendering stale & out of sync data. If it were updating correctly, it would have had the same infinite loop problems.
Exactly this. Class components let you cheat and easily write components that were broken if a prop was unexpectedly updated. Hooks will surface this immediately.
This is definitely not the case. I’ve seen enough class components with complicated componentDidUpdate methods. The method would then be broken up into multiple other smaller methods (or to re-use some logic in didMount and didUpdate)
A few iterations later and you have setState peppered throughout your component.
More often then not though I’ve seen a lot of class components that would just fail to update when certain props change. It’s much harder to miss these cases with hooks.
>It's unreal that in React I have to deal with occasional infinite loops now because of hooks. Sure, React catches the loop cycle so things don't totally freeze but I don't recall ever having to deal with this before them ... missing dependency accidents requiring linters to prevent
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.
Overall I think hooks are a fine addition to the React toolbox. But I think they are very easy to overuse and the complexity of hooks seems to increase exponentially. I've been involved in two code bases now where hooks are just everywhere and they were both an absolute nightmare. But I've also been involved in code bases where hooks are used more sparingly, about on par with when HoCs were used, and it's rather pleasant. In general, the more "dumb" components your app has, the more manageable it seems to be overall.
I feel that with class components I have a really good understanding of what is rendering and most importantly, when. componentDidMount, shouldComponentUpdate, PureComponent, etc. With hooks, it's much more magic. And clarity of rendering is literally the one thing I want from React.
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.
This is how I feel too, and I'm a confused how the reaction to hooks is so overwhelmingly positive. I find it quite strange that we need to set up an eslint rule to make sure our function arguments are correct, and it will automatically fill them out if we don't. And I need to memoize so many things! I feel like I'm not even writing javascript anymore.
This is the most legitimate criticism I've seen in the discussion. Hooks give you more control. 'useEffect' will only re-run when any value in the dependency array is updated. In class syntax, you have 'componentDidUpdate', but that function gets called after any prop or state change. With hooks, there is more granularity. Personally, I've found reasoning about hooks to be a learning curve that was conquerable in about a week. But there is no arguing, it requires you to mentally reason a little bit further than the blunt 'componentDidUpdate'.
It's a bit of a trope by now, but there is a lot of truth in the common argument that if converting your class component to hooks makes it feel more complicated, you probably had subtle bugs in your class component -- usually an edge case you hadn't bothered to handle. The main quirk of hooks is that it makes problems in your components a lot more visible. I don't view this as a bad thing, but I totally get that it's frustrating.
My only issue with Hooks has been that they are not inputs into the component. It's a step in the right direction of making React more functional I would just preferred less magic, personally.
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.
That's not the only problem with your approach. It is extremely common to use the output of one hook in the input of another, and that's only possible if the hooks exist in the function body.
I don't really understand how this approach breaks what you are describing.
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.
Oh, I thought you meant that the hooks would be called there, which was one of the many alternative proposals made after hooks were announced.
In any case, it still wouldn't work because hooks are composable. You can create your own custom hooks outside of components which can be used as if it was one of the primitive hooks. That's not possible if the primitive hooks can't be accessed outside the component scope, unless they're passed in as parameters every time the custom hook is called (and that would be a right pain in the backside).
Hooks elucidate everything I've felt wrong about React, but have not been able to put my finger on it until recently.
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.
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.
RE: 1) "it is that components cannot reuse logic" - +1 to this - recently I re-watched original hooks talk by Dan Abramov and was not able to finish it with conclusion different than "you guys really fix issues you invented before". Class-based components and reusability of logic is something that existed years before React, and probably will exist years after React. Even this concept of Dependency Injection and Services that Angular is still on proves that there-are-solutions. There are solutions for reusing logic between classes. Thing that bothers me the most is not that there's something wrong with fixing your own issues, but the fact that developers outside React Core Team start to think that "well, you cannot reuse logic between components".
Angular does not have a solution for this as far as I know. It's not about re-using logic. It's about re-using reactive logic that ties in to your component's lifecycle.
The "idea" that react hooks try to implement is very common in FP languages though. It has a lot of parallels with extensible effects; which is a pure, functional embedding of the idea of hooks
Who says functions don't have state? Referential transparency requires no such constraint; it only requires that state not leak into or out of a pure function save through its arguments (inward) and return value (outward). Beyond that, what they do within the space of their own lexical scope and the lifetime of their call stack frame is entirely their own business.
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".
> it'd be surprising in any case to encounter dynamic scope in Javascript, a language which does not even support it.
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.
I think this is mostly a disagreement around terminology.
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.
One way or another it's fine to consider methods that explicitly use state as 'not functions'. This is the mathematical definition after all.
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.
> how silly it is that components cannot reuse logic
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.
> React developers do not understand functional programming
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.
Yeah, I'm not really plugged into the community that well but I think reasonml.org is the long term plan and that some sort of transition might be going on? I've mostly used reasonml.github.io and Bucklescript docs, but lately I've started using the Discord channel and it's very welcoming, friendly and helpful!
Huh? You can treat state as arguments to your function. For a given function evaluation, the state is stable. The fact that state can be changed during event handling is 100% irrelevant to the evaluation. There is no dynamic scoping.
I feel you don't understand how React function components work. From a purely functional standpoint everything a function should have access to must be passed in as an argument. Any state that exists is always external to the function. Therefore hooks like useState can't exist because it creates state within a function. This is not only weird from a purely functional perspective it's also incredibly weird from a regular OOP perspective because that state should have been declared inside a class or struct. Therefore it is neither pure FP nor pure OOP. It's a random mix of both but it fools beginners into thinking that because it is not OOP it surely must be functional programming.
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.
Can you please give an example of React hooks 'dynamic scoping'? See https://stackoverflow.com/a/22395580 for an introduction to dynamic scoping.
React hooks 'state' variables are scoped to a single function. They are not arbitrary variables, but represent inputs to the program. The mental model is a function that produces the same output given the same inputs. Think of a dropdown, where the associated state variable D my have values a, b or c, depending on what the user chooses. The render function simply does not care how the value of D was set, and renders the exact same jsx given a specific D value: jsxa for a, jsxb for b and jsxc for c. That is as pure as it gets. Furthermore, the dropdown state variable D never represents the intermediate computation of some other component[s], and it's never changed by other components arbitrarily based only on the variable name.
The use of "dynamic scoping" to describe React Hooks state is unnecessarily imprecise, implying that a fairly well designed system is a specific kind of mess. Please don't engage in FUD.
Tip: Never call setFoo functions from render code. Only call setFoo from event code.
When saying that "functions do not have state" without mentioning monads and encoding effects in type systems, it doesn't sound like you have enough experience with functional programming to assert your second claim.
There's yet another valuable hooks critique that I recommend you to read - https://typeofweb.com/wady-react-hooks/ (Use Google Translate to convert Polish to English)
297 comments
[ 3.0 ms ] story [ 286 ms ] threadThe 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?
I covered some of the tradeoffs in this post and talk:
https://blog.isquaredsoftware.com/2019/07/blogged-answers-th...
https://blog.isquaredsoftware.com/2019/09/presentation-hooks...
- 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.
[0] https://reactjs.org/docs/hooks-intro.html#classes-confuse-bo...
React is a great framework, and super intuitive on so many fronts, but where it misses, it misses big. To come to the conclusion that the Class model, a pretty predictable pattern, is more a barrier to entry, or a conduit for confusion, is really misguided.
Just take a look how simple functional components can take on a quasi Class-like form with lookup maps for hooks in this particular article. When you advocate for stuff like this, you are injecting the community with effectively bad practices.
The React dev team can not resort to the ‘You probably don’t need ________’ article in perpetuity. At some point, they have to say to themselves - ‘We probably don’t need to add this to the API’.
On topic, it has been my understanding that the React team is moving away from classes for a number of reasons. Not just because it confuses people (although the pattern seems to create expectations that aren't met due to Javascript's weird 'this' behavior), but also because it doesn't have a good pattern for code reuse (as demonstrated by the lack of mixins that used to be popular in React's createClass syntax) and also because it seems to be a suboptimal pattern for compilers.
That said, I've moved on from class components to function components with hooks and I can't remember the last time I thought something would be easier to implement as a class component, even though it is still an option to do so. But that is, of course, anecdotal.
Not saying it's good or bad, just an observation
All exist because of the idiosyncrasies of `this` that you may be discounting because of your familiarity and already being over the learning curve that ensnares people daily.
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.
And on a day-to-day basis for me, there are still plenty of uses for reusable logic inside of components even after extracting business logic / creating small focused components, especially as it pertains to presentation. A simple example that I use somewhat frequently is a window-size watcher.. a pretty simple hook that watches the window-size, re-renders when it changes (on a debounce), and it provides the current width x height of the window, allowing the component to use those values to calculate some view parameters. With hooks, it's as simple as plopping `const { windowHeight, windowWidth } = useWindowSize()`. Without hooks it generally requires wrapping a component with another.
I've been using hooks basically since they came out and IMO they're way more in tune with React's programming model than class-based components. Even if you create custom hooks for most components, the paradigm still encourages developers to encapsulate related pieces of component logic into their own hook-functions rather than spreading that logic across multiple lifecycle methods. I haven't come across a single situation where I would have preferred a class based component.
1. Some stuff that happens when the component gets mounted
2. So local state that gets maintained (shown error text, whatever, red x, whatever)
3. Some stuff that happens when the component is unmounted.
React classes fit 99% of my use cases. Yes HOCs to get navigation and stuff working is a pita. But honestly I'd have preferred a way to get the cool hook stuff added to class components instead of adding all the class component stuff to function components!
For the simplest cases, class components might be clearer with its clearly labeled lifecycle methods. But most of the time, if I have multiple different things happening on mount, hooks are definitely simpler as you can divide / encapsulate based on feature functionality rather than lifecycle.
This is my favorite example for showing how this effects code structure: https://twitter.com/threepointone/status/1056594421079261185
If you then extract those bits into custom hooks in order to name that piece of functionality (e.g. in that example, useTitle() and useWindowResize()), the clarity improves dramatically.
[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`.
I find my older pure JS codebases so, so much easier to go back and remember what was going on than some of the React stuff I've had to interact with in the past 5+ years - and I say this as someone who has written, released, and taught React.
The problem for me is, I think, I'm not sure you can do better than React. You'll either wind up with some complicated beast from hell (Angular) or veer off into performance-benchmark-overvaluing (Svelte).
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.
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.
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.
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.
So there doesn't seem to be a huge amount to learn in hooks as far as I can tell.
* I am an experienced React developer but haven't had much opportunity to work with it in the past 2 years.
There's two aspects to learning hooks:
1. Learning the API. This is not a "conceptual" part of learning but rather "this function name does this thing". This is the same with learning any programming API and is almost always non-transferrable (with the minor exception of some open standards, except that varying implementations of them still tend to have quirks).
2. Learning the patterns and concepts around applying that API to problems. As far as I've seen just from TFA examples, they're very widely applicable. Memoization is widespread. Functional style is widespread. The most complex stuff handled by the quoted examples is maintaining state in nested hashtables, which is such a widespread concept that observable/immutable libraries like MobX et al & ImmutableJS et al have been written pretty much focused entirely on this problem space.
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.
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.
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.
Change for the sake of change is not a sound argument.
The thing about hooks is they don't enable a single thing we couldn't already do with HOCs. They are also much harder to read, because stateful logic is now just sprinkled around your render method rather than being isolated to places you know to look for it. I won't be using hooks ever, as far as I'm concerned.
* "A Critique of React Hooks"
* "A Critique of the Change Costs Induced by React Hooks".
Objections to React Hooks are stronger if they don't invoke change costs as the primary three concerns.
And lastly, let’s not minimize change-cost. In the real world, it’s a cost. We’re all willing to pay it if it’s necessary, or affordable, but not because someone showed up and said ‘change please’.
https://www.reddit.com/r/reactjs/comments/g2pda4/functional_...
Yes, but how long is long term?
jQuery hasn't radically changed its methodology in almost 15 years.
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 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/
[1]: https://overreacted.io/
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:
With Hooks, that doesn't apply. The useState "set" function doesn't have a similar argument. Instead, you need to use 'useEffect' with an optional argument: 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
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(); } ```
https://overreacted.io/algebraic-effects-for-the-rest-of-us/
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.
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
https://twitter.com/dan_abramov/status/1093697963350810624
https://twitter.com/dan_abramov/status/1093698629708251136
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.
It's interesting. Everything is from scratch. No depa what so ever.
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.
Yes, the "V in MVC" term came straight out its main page:
> JUST THE UI
> Lots of people use React as the V in MVC. Since React makes no assumptions about the rest of your technology stack, it's easy to try it out on a small feature in an existing project.
https://web.archive.org/web/20140321012426/http://facebook.g...
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
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...
After reading it, I finally feel like I'm starting to understand where React came from, why it's designed the way it is.
The paradigm shift that React brought to JavaScript was to "bend the language" to implement concepts and design patterns from ML, a functional language with roots in Lisp, with static typing, algebraic data types, and foundation in lambda calculus and category theory.
When you joked that it was "shoehorned" into JS, I got an insight into the reason why some design decisions in React feel awkward and strangely non-idiomatic. It explains, in part, the strong emotional reactions seen in this discussion thread, a number of justified opinions, its problems as well as benefits.
I've been skeptical of the design of React Hooks, and still am, but now I'm interested in learning its influences, to understand the logic behind them. I wish that it had been implemented to be more "React-agnostic", like JSX, as generic extension to the JavaScript language.
Ed: as an example, I found this (oldish) post on hooks in reasonreact:
https://dev.to/iwilsonq/reasonml-with-react-hooks-tutorial-b...
Compare as you will with a comparable one for reactjs:
https://upmostly.com/tutorials/build-a-react-timer-component...
Note - I would advice looking at the reasonreact documentation for up to date tutorials and guides... It's a quickly changing landscape.
Eg:
https://reasonml.github.io/reason-react/docs/en/usereducer-h...
Finally I came across this - I thought maybe a sibling comment mentioned it - but a quick search didn't turn up anything - but imo it's a pretty strong argument for hooks (in js react) :
https://tylermcginnis.com/why-react-hooks/
That last one I like, especially where the author recommends to "forget everything about lifecycle methods" and think of hooks in the context of synchronization. It makes sense, as a declarative way to describe state and state transitions.
The aspect that's unsettling is that they're not idempotent pure functions, but rather deeply tied in with how React works internally. They can't be used outside of React, and require the programmer to understand the magic that makes them stateful.
The common issues that beginner users of hooks encounter, like the "captured scope" of variables, or that hooks must be run in the same order every time, never conditionally - I suppose these are some reasons that make me (and other hook skeptics) react to them as "code smell". If someone had designed a library totally unrelated to React this way, I wouldn't want to use it.
Looking at ReasonML, it's quite elegant and intuitive how React Hooks fit in. In fact, the code examples look very similar to how I structure my React projects, with state and actions (instead of a reducer or Redux, they use immer for immutable state changes).
I'm staying open-minded about hooks, and I think the more I learn of its roots, the more I'll come around to accepting them as part of idiomatic React.
- Seb Markbage: general deep thinking and vision, works on Suspense and some of the server rendering
- Andrew Clark: implemented much of the Suspense and Concurrent Mode core
- Dan Abramov: started Create-React-App, wrote the hooks and Concurrent Mode docs, works on various parts of the library and tooling
- Brian Vaughn: rewrote the React DevTools, added the Profiler, recently implemented the upcoming `useMutableSource` hook
- Dominic Gannaway: previously created Inferno, now works on a lot of the upcoming new event system and other optimizations
- Nicolas Gallagher: previously created React Native Web, now also works on the event system
- Luna Ruan: newer to the team, helped implement the `useOpaqueId` hook that just got merged
- Rachel Nabors: currently working on revamping the docs and community outreach
They have a team bio page here:
https://reactjs.org/community/team.html
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.
I wonder what about generator functions?
I'd be surprised if generators didn't come into React at one point or another
"stateful": A function that has state, i.e. can store data
"with effects": A function that modifies data outside its own scope
Normal JS functions (as opposed to arrow function) already do this:
function foo(){};
foo.state = bar;
Generator functions take it a step further, where the function remembers internal state between calls.
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.
https://crank.js.org/blog/introducing-crank
Crank itself is interesting, but what's relevant here is the broader critique of React there.
https://news.ycombinator.com/item?id=22903967
A few iterations later and you have setState peppered throughout your component.
More often then not though I’ve seen a lot of class components that would just fail to update when certain props change. It’s much harder to miss these cases with hooks.
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.
This is what you should be doing and not doing this seems more likely to cause problems. Do you have an example?
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.
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.
In any case, it still wouldn't work because hooks are composable. You can create your own custom hooks outside of components which can be used as if it was one of the primitive hooks. That's not possible if the primitive hooks can't be accessed outside the component scope, unless they're passed in as parameters every time the custom hook is called (and that would be a right pain in the backside).
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.
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.
https://docs.racket-lang.org/reference/boxes.html
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.
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.
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.
React hooks 'state' variables are scoped to a single function. They are not arbitrary variables, but represent inputs to the program. The mental model is a function that produces the same output given the same inputs. Think of a dropdown, where the associated state variable D my have values a, b or c, depending on what the user chooses. The render function simply does not care how the value of D was set, and renders the exact same jsx given a specific D value: jsxa for a, jsxb for b and jsxc for c. That is as pure as it gets. Furthermore, the dropdown state variable D never represents the intermediate computation of some other component[s], and it's never changed by other components arbitrarily based only on the variable name.
The use of "dynamic scoping" to describe React Hooks state is unnecessarily imprecise, implying that a fairly well designed system is a specific kind of mess. Please don't engage in FUD.
Tip: Never call setFoo functions from render code. Only call setFoo from event code.
> React, like socialism, perfectly solves the problems that it created.