Dev Thoughts: Jiří Staniševský on React Hooks
Since their introduction, React Hooks have been enabling developers to write React applications with only functional components. But is that enough to drive general adoption? I spoke with Full-Stack Developer Jiří Staniševský, who answers that question and more.
Table of Contents
Since their introduction, React Hooks have been enabling developers to write React applications with only functional components. But is that enough to drive general adoption? I spoke with Full-Stack Developer Jiří Staniševský, who answered that question and more.
What do you like most about React Hooks?
There are plenty of benefits to using React Hooks, such as a flatter virtual DOM tree, perfect TypeScript support, cleaner separation of concerns within a single component, and more. The biggest advantage for me, however, is the ability to define my own custom hooks. Most advanced React developers have probably faced a situation similar to this multiple times:
Your component does a lot, but you don’t want to over-engineer things, so you keep all the logic inside it. After some time, you find yourself creating another component that overlaps with at least 50% of the functionality of the first one. Now it’s time to do some refactoring and hoisting of the logic.
Before React Hooks, the logical steps were to pick the bits of functionality you needed, move them to a higher order component, pass all the handlers and data through props to the actual component and make sure to patch that one so that it doesn’t bleed out after this invasive surgery.
With React Hooks, the extraction is simply a matter of cutting the code from the component and pasting it into a plain function. This is possible because both the component and the new hook are simple functions and they use the same code structure. The extracted hook fits into the modified component without any additional work. The only thing that changes is how the variables are initialized. Furthermore the new hook immediately becomes available to any new component.
Here’s an example of a component that renders time elapsed since a specific moment:
Additionally, while declaring the new function, you have to give it a clean and self-explanatory signature. Struggling with this often reveals flaws in your original design and thus leads to improved readability and maintainability of your code-base.
Declaration and usage of higher order components (HoC) can be annoying. This usually makes the developer bundle as much functionality as possible into a single HoC so that he doesn’t have to deal with multiple ones.
Hooks are tiny functions focusing on elementary functionality, which makes them easier to comprehend, test, and maintain. If more complex functionality should be wrapped into a custom hook, it’s good practice to compose that hook from some smaller ones. Understanding the smaller pieces makes it easier to make sense of the whole puzzle.
What don’t you like about React Hooks?
I don’t like most of the hype around hooks, especially when they first came out. Many people praised the functionality and testability of the code the hooks made without giving a sound example or reasoned argument. All this fuss negatively affected my interest in hooks. It took some persuasion to make me look into them more deeply. Once I did, I immediately fell in love.
The biggest flaw in the design of hooks is how the dependency arrays and state variables cooperate. While the idea of a dependency array that re-triggers an effect or recalculates a memoized value is brilliant, it also makes reaching the current state values from callbacks and effects quite difficult. Sometimes you simply don’t want the callback to be re-created or the effect re-triggered just because the value of a state variable changed.
The only current workaround is to misuse the
useRef hook and keep a reference to the current state value to reach it from any “stale” callback or effect. This often leads to an ugly pattern of having a state variable and a ref object pointing to it in the same component. Sometimes people “legalize” these hacks by wrapping this nastiness in hooks like
useStateAndRef, but that doesn’t change the fact that you still have two ways of accessing a single value.
When I first understood how hooks worked, I was a bit terrified by the amount of code that needed to be executed at each render-cycle and the number of arrow functions created just to be immediately thrown away. I looked desperately for a way to prune my “hook tree” in a similar way to how I prune the virtual DOM tree by using
PureComponents. In other words, it made no sense for me to execute all the hooks each time if their dependencies didn’t change.
Unfortunately there’s nothing like that in React. The requirement of calling all low-level built-in hooks in the same order within each render-cycle simply prevents you from doing any pruning yourself. Giving up on these (premature) optimizations took some time and I consider this to be an additional barrier for an experienced developer trying to start with hooks.
Nevertheless, in the end, I simply started ignoring these negligible imperfections the same way I did with constructs like
array.filter(...).map(...).reduce(...). The things we do for readability!
Can React Hooks replace Redux?
This is a continuation of the hype mentioned earlier. In this case, the Context API is also involved (which has been in React for quite some time). There are plenty of articles that discuss the ultimate way of “Replacing Redux with React Hooks and Context API”, but this is a misleading title as both React Hooks and Context API are low-level React tools. Furthermore, both of those tools are currently used by Redux, so it’s like claiming that a house can be replaced with a window and a brick.
Don’t get me wrong, I’m not an advocate of Redux or, God forbid, Redux Sagas. Implementing similar functionality to Redux is not straightforward and either requires a lot of effort or accepting a suboptimal solution that may backfire in complex projects.
Do you prefer using Redux with hooks or connect?
Definitely hooks. I find using any HoC cumbersome and, if you involve TypeScript, you’re up for a sleepless night of either bending the strict typing system or writing everything three times. Now imagine that there’s more than one HoC attached to the component.
Another serious reason for ditching the
connect HoC is that it can be used only with components, therefore any kind of custom hook cannot be directly fed with data from the store. Through
useSelector you can simply request specific data from within the hook itself, so that it works as a self-sufficient unit.
The strongest argument for using connect (and basically any HoC) is that you implicitly split the component into two layers: one responsible for rendering the data and one that feeds the data in. This should theoretically result in better testability of your components as you can test the layers separately. I don’t see much value in standalone component tests, unless you’re writing a component library, as they’re usually limited to the “component rendered without error” kind.
Feeding the components with static snapshots of data and expecting static snapshots of virtual DOM structure doesn’t give us much safety. Once you start using state in your functional components they stop being pure and idempotent, therefore doing snapshot testing stops being enough.
Sure, there are ways to orchestrate full flow through a component, but isn’t it better to run the show through a mocked Redux and include the data-feeding layer into the test as well?
For which types of projects would you recommend using React Hooks?
When I did my first hooks-powered project, many of the third-party packages were either in the adoption phase or did not support them at all. Even with some aspects still requiring use of HoCs, this was the cleanest React project I’d ever done. Now, with the vast majority of popular React libraries already supported (or converted to) hooks, I don’t see any reason why I wouldn’t recommend React Hooks for any future project.
Using hooks as the backbone technology of your application doesn’t prevent you from reaching for a standard class component from time to time, in case you need more precise control—not that there are many situations which you can’t solve with hooks alone.
About Jiří Staniševský
Jiří Staniševský is a Full-Stack Developer who works with TypeScript, React, React Hooks, Node JS, and other technologies. What gives him the most pleasure is creating reusable components, small libraries, and frameworks. Out of the office he enjoys spending time with his two daughters and re-watching classic episodes of The Simpsons.