Maybe I am crazy or old school but I though the concept of creating component as a class with standard functions to override (and defining a contract with props types) took a unwieldy language like JS and gave a sane and standard approach to it.
I now look at more "modern" functional components /w hooks and find it VERY hard to understand the data a component takes and how it uses/responses to interactions.
It feels like react was class based... and now that functional coding is the "in thing" its trying to shoehorn everything into functional components. In the end it really turns me off to functional programming as I see many developers sacrificing readable/understandable components to save a few lines of code.
Hooks address a very specific problem with classes: composability.
You can use the various builtin hooks to do stuff that (in class-based React) previously required implementing methods (eg. componentDidUpdate). Using those methods works fine until you want to split out and reuse part of those methods in other components.
You can certainly try class composition: creating little classes that you call in each of the relevant lifecycle methods. However this adds a lot of boilerplate to components which use the composed objects (as is common with class composition). It's also awkward to share the setState API with these composed classes.
The Custom Hooks approach[1] with React Hooks just makes it really easy to use state and lifecycle functionality when extracting common behaviour to can be shared between multiple components.
React has tried to address this composition issue several times in the past, and I think hooks are the nicest yet:
React's old 'Mixins' feature got a lot of the way there, but still didn't compose perfectly. For example, you had to avoid method and state key name collisions between mixins.
Creating a 'Higher Order Component' is another approach, which is less verbose than class composition, but makes your component tree deeper, and can also be annoying to statically type.
The community also came up with 'Render Props' to address much the same issue.
And of course, you can always just continue to use classes if you aren't experiencing any of these pain points.
Hooks just compose quite nicely in cases where none of these other approaches do.
Is all this because inheritance is discouraged? Seems weird to got to that much trouble to avoid a builtin language feature that does the thing you want, which is reuse methods across components.
Agreed. The "everything needs to be a react component" is bad IMO. My example, I'm making an image viewer. I want a context menu. IMO the ideal solution some generic right-click (context menu) function/handler. you attach it to each image. On right click the image is passed to the handler and the handler does something with it. That's relatively low overhead. There's no per image data.
But the React stuff I found made a context menu of nested components per image. Effectively <image><contextmenu><menuitem/><menuitem/><menuitem/><menuitem/><menuitem/></contextmenu></image>. For 1000 images that's 6 thousand components created, 6000 things added to the virtual dom to be diffed, 11000 considering the content of each menuitem is actually yet another dom node.
There's tons of react libraries like that where they try to shovel every concept into a component.
This just sounds like inexperienced programmers. I've played around with React and it doesn't seem like there is anything special about React that would prevent you from creating a single instance of a component and have it's state update based on what image you are right-clicking on.
Look into portals https://reactjs.org/docs/portals.html. The menu should probably be owned by a shared component somewhere in the root of the tree, and then children can ask it to open via handlers propagated somehow (context, redux, whatever).
Same. React passed the, "this is obvious and it's clear where it fits into the real world" test for me years ago with class based components.
But these new features aren't passing that test at all. I struggle to grok what hooks do that I was missing before.
It gets me wondering if the React team is in a position to know when React is good enough and needs to be left alone for the most part.
The good news is that I seem to be able to just ignore hooks and remain very productive. I even started ignoring advice on when to use an SFC instead of a class and found myself happier and more productive with better code.
As another commenter note, the OP article discusses the pathological case. An API can make a hundred cases simpler but discussions will always focus on the few ones that got harder. The point of this post is to show how to work through this case and why the _end result_ is more flexible and powerful than before.
I have the same feeling. I would prefer to use classes rather than functions. More natural and nicely work with Typescript.
Luckily React is only a library and it is up to you how do you use it. I got used to working with classes and objects. For bigger and serious project I prefer Ember.js, which is modern and easy to pick up (https://yoember.com), but for some tiny project React is also a good choice with Typescript.
I guess hooks is a good option for devs who prefer functional programming.
Classes aren't really that great in React. You typically need do a lot of juggling to co-ordinate lifecycle hooks. Trying to do something like register, update and unregister a listener feels very verbose with classes because you don't truely control the instance lifecycle – React does.
Hooks work just fine in Typescript. Not just because Typescript already does will with Functional Programming, but because the types of most hook functions are generally rather simple.
`declare function useState<T>(startState: T): [T, (nextState: T) => void]` is pretty simple function signature and most cases Typescript picks up (infers) that T generic parameter for you based on startState. Almost every other Hook function has a similarly simple type signature, despite the complicated "guts" of how React keeps track of hooks internally. (Which also doesn't seem all that complicated, relatively speaking.)
(I still want a way to represent the order of effects / no effects in control flows rules in Typescript's type system, but linters will handle it fine. At this point it's mostly just a personal toy project/curiosity because Typescript feels like it has enough of the basic building blocks in the type system such as `never` to get quite close.)
React didn't even start out with classes — React.createClass came first. It allowed you to define lifecycle hooks for a component, but wasn't supposed to be a generic class implementation.
There is some difficulty in understanding how this works at first if you haven't hit these problems before. But once you do, the payoff is immense.
You think of problems in terms of describing how data changes over time rather than how am I going to make this display. The JSX part of the code becomes an afterthought. In the same way you aren't thinking about what DOM mutations React is actually doing behind the scenes, don't worry too much about how it wires together just describe what it does. Counter is a wonderful example. Look how contained and directed the hooks version is compared to jumbled mess the class version is. Picture that class component was anymore complicated, tracing through these lifecycle methods to figure out what is happening under any number of different input changes. Where the hook tells you in its definition when it updates. Each new behavior is just listed after and self-contained. Consider how refactoring and breaking apart components works in both scenarios.
This paradigm is not new. New to React but not at all new to Frontend JS UI libraries. Ironically when presented in MobX, and KnockoutJS(way back in 2009) it was considered simple. Somehow in 2019 with how far we have progressed, it's too difficult?
Calling the class component a "jumbled mess" is nothing short of hyperbole. It could be simplified quite a bit however. The problem is an inappropriate use of "setInterval" here. If the author used a recursive "setTimeout" function instead, the entire "componentDidUpdate" function can be deleted. Alternatively, keep the setInterval and all the logic in componentDidUpdate could just be moved to the tick function.
This contrived example hasn't sold me on hooks at all.
Wow, I really don't know what to think of this article. I have to give Dan credit that every time I thought the code started looking really crazy, he'd say something like: "Admittedly, [this] code can be disorienting. It’s mind-bending to mix opposite paradigms." And I think it's a good sign he recognizes the fair objections people will bring up.
In addition to the paradigm-mixing the article illustrates, I wonder if the actual hook APIs make things more confusing than necessary at some points. For example, in the `useEffect` hook API:
* the first argument is a function that on every render
* the return value of the first argument is a "cleanup" function which runs when the component is unmounted or before the effect is run again
* the third argument is an array of parameters which controls when the effect is run. If passed an empty array, the effect is only run at mount and cleaned up at unmount.
All the above means that the API is very implicit and doesn't self-document its intent all that well. An example, from the article:
useEffect(() => {
function tick() {
savedCallback.current();
}
let id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
I'm sure with more use these hook patterns will become more familiar. But it's clearly a lot harder to learn and understand than the original simplicity that helped propel React to popularity.
Dan's arguments that hooks provide for easier abstraction and more composability is interesting to me though - if hooks are going to be "worth it" in the long run, I suspect this is why.
The pattern of returning an unregister function is a common one. But I agree that the cache key array is strange.
I'd almost suggest that a seperate useEffectOnce function would be better, but I'm sure the abstraction was heavily tested and bikeshedded before it was released in this form.
I do React at work and Elm for personal projects, and I feel the React ecosystem is trying too hard to become something its not. Hooks to me are an added level of complexity, and doesn't simplify the way I write or test components. I don't really mind the class based components as I feel there is less magic.
Personally, React had a great run, but it's time to move on to a more purpose built language/framework.
I read half the article and got lost. What I like about the "original" React is that it is simple to understand, close to JavaScript in a sense that you do not need to learn that much and be able to have a good result. I'm reading more and more about Hooks and still have a hard time to have a clear idea of how to avoid these pitfalls. At the point I am, it seems that Hooks has a lot of hype from few big influencers but that the day-to-day developers will be lost.
I’ve now converted a good chunk of a large stack over to them and the best way to describe them is this: they are as big an improvement to React as React was to its predecessors.
This example is meant to be a pathological case. The fact that you can build complex interface logic and bundle it into a function that can then be extended and composed is a huge leap forward.
I do think Hooks will take time for some of the best ones to become standard. While I see some early “lodash for hooks” like libararies appearing, I could also see a really nice PWA that let you search gists for popular hooks or similar.
Hooks are a very nice solution to a very hard problem. But unless you’ve done large scale frontend work and especially more complex interactive components it may be hard to see. Although just getting rid of all the decorators/HOCs is already a big win.
Agreed, we're using hooks over redux on a largish codebase and once you wrap your head around the model the only real frustration is not being able to use them in class components.
> The fact that you can build complex interface logic and bundle it into a function that can then be extended and composed is a huge leap forward.
Unless you already used HOCs (in the Recompose sense).
Every single blog post is obsessed with comparing hooks with classes (I guess because it's the only thing they improve upon). What about HOCs? I'm not sure hooks are really that much of a leap forward compared to them.
HOCs only wart is their pollution of props which admittedly hooks solve (by virtue of using local variables), but hooks have their own warts too as evidenced in this article.
Perhaps I'm just a bit dumb here, but a few of these comments confused me.
> However, this is a common source of mistakes if you’re not very familiar with JavaScript closures.
> The problem is that useEffect captures the count from the first render. It is equal to 0. We never re-apply the effect so the closure in setInterval always references the count from the first render, and count + 1 is always 1. Oops!
Perhaps I'm not understanding the second comment fully... This behavior is completely counter to how Javascript closures work in my mental model. I'm trying to figure out how a state hook differs in execution from a normal variable in module scope.
The difference is that closedVariable would be created inside intervalFn (the component). In addition, the intervalFn may run many times, but the interval will only be set up once. This example illustrates the problem: https://jsfiddle.net/ov2msa4e/
Your code avoids the closure problem, but closedVariable would be more like a static variable — it'd be shared by every instance of the hook. Probably not what you intended!
The difference is subtle (and took me a bit to figure out!). In the article's example component, the component's "main" function gets re-run on every render and a new `count` variable is created each time. The effect function is only run once at the very first render, and only captures the value from this first render.
I'm having a little trouble explaining the difference well, I think this jsfiddle illustrates the incorrect case the article is talking about representatively: https://jsfiddle.net/7fLnvz5c/
Thanks Jason, this was very helpful for me. I think I was having trouble making the mental jump from reading the code as a normal function with state vs how a component instance is called and handled by React at runtime--your example helped me make that hurdle.
> Our “impedance mismatch” is not between Databases and Objects. It is between the React programming model and the imperative setInterval API.
I haven't used it much, but React gives me the feeling that it's trying to come up with hacks to shoehorn imperative UI (which seemingly can't go away) into the "the state is the UI" model. Anything with timers, or animations, or other things that reach across multiple renders seems to be poke through the nice React model because it by definition requires dropping the abstraction of the UI being defined by the state because the change is more "incremental" than transitioning to a new value.
(As an aside, I love the dark mode support on your website!)
> React gives me the feeling that it's trying to come up with hacks to shoehorn imperative UI
React IS a hack to shoehorn declarative UI into an imperative language and DOM system. All further progress in the field is going to be a collection of hacks.
ReasonML, Elm etc are clear ways forward that attempt to start from a clean slate, but you're trying to do declarative UI on JS+DOM, everything is going to be a hack. React is just the nicest currently available hack.
By this logic, though, everything declarative is a hack. Computers are imperative. At some point everything becomes an instruction; dig deep enough and all declarative code calls imperative code.
I now look at more "modern" functional components /w hooks and find it VERY hard to understand the data a component takes and how it uses/responses to interactions.
It feels like react was class based... and now that functional coding is the "in thing" its trying to shoehorn everything into functional components. In the end it really turns me off to functional programming as I see many developers sacrificing readable/understandable components to save a few lines of code.
You can use the various builtin hooks to do stuff that (in class-based React) previously required implementing methods (eg. componentDidUpdate). Using those methods works fine until you want to split out and reuse part of those methods in other components.
You can certainly try class composition: creating little classes that you call in each of the relevant lifecycle methods. However this adds a lot of boilerplate to components which use the composed objects (as is common with class composition). It's also awkward to share the setState API with these composed classes.
The Custom Hooks approach[1] with React Hooks just makes it really easy to use state and lifecycle functionality when extracting common behaviour to can be shared between multiple components.
React has tried to address this composition issue several times in the past, and I think hooks are the nicest yet:
React's old 'Mixins' feature got a lot of the way there, but still didn't compose perfectly. For example, you had to avoid method and state key name collisions between mixins.
Creating a 'Higher Order Component' is another approach, which is less verbose than class composition, but makes your component tree deeper, and can also be annoying to statically type.
The community also came up with 'Render Props' to address much the same issue.
And of course, you can always just continue to use classes if you aren't experiencing any of these pain points.
Hooks just compose quite nicely in cases where none of these other approaches do.
[1]: https://reactjs.org/docs/hooks-custom.html
But the React stuff I found made a context menu of nested components per image. Effectively <image><contextmenu><menuitem/><menuitem/><menuitem/><menuitem/><menuitem/></contextmenu></image>. For 1000 images that's 6 thousand components created, 6000 things added to the virtual dom to be diffed, 11000 considering the content of each menuitem is actually yet another dom node.
There's tons of react libraries like that where they try to shovel every concept into a component.
So your <image> code would look something like this:
Ideally, you would only have a single event listener that handles events for all images.
But these new features aren't passing that test at all. I struggle to grok what hooks do that I was missing before.
It gets me wondering if the React team is in a position to know when React is good enough and needs to be left alone for the most part.
The good news is that I seem to be able to just ignore hooks and remain very productive. I even started ignoring advice on when to use an SFC instead of a class and found myself happier and more productive with better code.
I wrote about this too:
https://medium.com/@dan_abramov/making-sense-of-react-hooks-...
Hope it helps.
As another commenter note, the OP article discusses the pathological case. An API can make a hundred cases simpler but discussions will always focus on the few ones that got harder. The point of this post is to show how to work through this case and why the _end result_ is more flexible and powerful than before.
Luckily React is only a library and it is up to you how do you use it. I got used to working with classes and objects. For bigger and serious project I prefer Ember.js, which is modern and easy to pick up (https://yoember.com), but for some tiny project React is also a good choice with Typescript.
I guess hooks is a good option for devs who prefer functional programming.
`declare function useState<T>(startState: T): [T, (nextState: T) => void]` is pretty simple function signature and most cases Typescript picks up (infers) that T generic parameter for you based on startState. Almost every other Hook function has a similarly simple type signature, despite the complicated "guts" of how React keeps track of hooks internally. (Which also doesn't seem all that complicated, relatively speaking.)
(I still want a way to represent the order of effects / no effects in control flows rules in Typescript's type system, but linters will handle it fine. At this point it's mostly just a personal toy project/curiosity because Typescript feels like it has enough of the basic building blocks in the type system such as `never` to get quite close.)
Deleted Comment
You think of problems in terms of describing how data changes over time rather than how am I going to make this display. The JSX part of the code becomes an afterthought. In the same way you aren't thinking about what DOM mutations React is actually doing behind the scenes, don't worry too much about how it wires together just describe what it does. Counter is a wonderful example. Look how contained and directed the hooks version is compared to jumbled mess the class version is. Picture that class component was anymore complicated, tracing through these lifecycle methods to figure out what is happening under any number of different input changes. Where the hook tells you in its definition when it updates. Each new behavior is just listed after and self-contained. Consider how refactoring and breaking apart components works in both scenarios.
This paradigm is not new. New to React but not at all new to Frontend JS UI libraries. Ironically when presented in MobX, and KnockoutJS(way back in 2009) it was considered simple. Somehow in 2019 with how far we have progressed, it's too difficult?
This contrived example hasn't sold me on hooks at all.
In addition to the paradigm-mixing the article illustrates, I wonder if the actual hook APIs make things more confusing than necessary at some points. For example, in the `useEffect` hook API:
* the first argument is a function that on every render
* the return value of the first argument is a "cleanup" function which runs when the component is unmounted or before the effect is run again
* the third argument is an array of parameters which controls when the effect is run. If passed an empty array, the effect is only run at mount and cleaned up at unmount.
All the above means that the API is very implicit and doesn't self-document its intent all that well. An example, from the article:
I'm sure with more use these hook patterns will become more familiar. But it's clearly a lot harder to learn and understand than the original simplicity that helped propel React to popularity.Dan's arguments that hooks provide for easier abstraction and more composability is interesting to me though - if hooks are going to be "worth it" in the long run, I suspect this is why.
I'd almost suggest that a seperate useEffectOnce function would be better, but I'm sure the abstraction was heavily tested and bikeshedded before it was released in this form.
function useEffectOnce(callback) { useEffect(() => { return callback() }, []) }
Personally, React had a great run, but it's time to move on to a more purpose built language/framework.
This example is meant to be a pathological case. The fact that you can build complex interface logic and bundle it into a function that can then be extended and composed is a huge leap forward.
I do think Hooks will take time for some of the best ones to become standard. While I see some early “lodash for hooks” like libararies appearing, I could also see a really nice PWA that let you search gists for popular hooks or similar.
Hooks are a very nice solution to a very hard problem. But unless you’ve done large scale frontend work and especially more complex interactive components it may be hard to see. Although just getting rid of all the decorators/HOCs is already a big win.
Unless you already used HOCs (in the Recompose sense).
Every single blog post is obsessed with comparing hooks with classes (I guess because it's the only thing they improve upon). What about HOCs? I'm not sure hooks are really that much of a leap forward compared to them.
HOCs only wart is their pollution of props which admittedly hooks solve (by virtue of using local variables), but hooks have their own warts too as evidenced in this article.
> However, this is a common source of mistakes if you’re not very familiar with JavaScript closures.
> The problem is that useEffect captures the count from the first render. It is equal to 0. We never re-apply the effect so the closure in setInterval always references the count from the first render, and count + 1 is always 1. Oops!
Perhaps I'm not understanding the second comment fully... This behavior is completely counter to how Javascript closures work in my mental model. I'm trying to figure out how a state hook differs in execution from a normal variable in module scope.
How does it differ from this [1] example.
[1] https://jsfiddle.net/xuz09cow/1/
Your code avoids the closure problem, but closedVariable would be more like a static variable — it'd be shared by every instance of the hook. Probably not what you intended!
I'm having a little trouble explaining the difference well, I think this jsfiddle illustrates the incorrect case the article is talking about representatively: https://jsfiddle.net/7fLnvz5c/
I haven't used it much, but React gives me the feeling that it's trying to come up with hacks to shoehorn imperative UI (which seemingly can't go away) into the "the state is the UI" model. Anything with timers, or animations, or other things that reach across multiple renders seems to be poke through the nice React model because it by definition requires dropping the abstraction of the UI being defined by the state because the change is more "incremental" than transitioning to a new value.
(As an aside, I love the dark mode support on your website!)
React IS a hack to shoehorn declarative UI into an imperative language and DOM system. All further progress in the field is going to be a collection of hacks.
ReasonML, Elm etc are clear ways forward that attempt to start from a clean slate, but you're trying to do declarative UI on JS+DOM, everything is going to be a hack. React is just the nicest currently available hack.