React Hooks – revolutionary changes in React
Table of Contents
In February 2019, a new version of the React library was released. It was signed with the number 16.8.0. Solutions introduced with it revolutionized the whole library. So let`s take a closer look at the new features called React Hooks. As a developer who had the chance to become familiar with the older React libraries I would like to discuss not only the advantages, but also some drawbacks of the new solutions.
What are React Hooks used for?
This library is used to build dynamic user interfaces by linking and nesting components. These components are interface elements that perform a specific role. Components in React can be created in two ways – by defining a function or class in accordance with the ES6 standard. Before React Hooks was introduced, class components greatly expanded the capabilities of functional components whose task was only to present data. Currently, the user interface can be built very often without class components, but React Hooks bring a lot more to the React library.
React Hooks provide the ability to handle local component state (useState), component lifecycle support (useEffect) or simplified use of the Context API (useContext) inside functional components. React Hooks also perform such specialized tasks as handling the reference mechanism (useRef and useImperativeHandle) or extended state (useReducer) in many places similar to what you can meet while working with the Redux library. React Hooks also facilitate code optimization (useMemo and useCallback) or reading the state of the document object model (useLayoutEffect) known as DOM. Additionally, useDebugValue can be used to display labels for custom Hooks in React DevTools.
Pros and cons of using React Hooks
Along with the introduction of the discussed options, certain restrictions appear. Namely, for the proper functioning of the application, each time a function component is re-rendered, the order in which React Hooks is executed must be the same. Therefore, they should not be called inside conditional statements, loops, or nested functions. To understand this better, let’s assume that we have 2 hooks invoked inside a component. Re-rendering this component will result in a return the value of the first array element and the setter of the first array element, and then the value of the second array element and the setter of the second array element. The result of the operation would be invalid if the order of calls was changed. Additionally, hook names should start with the prefix “use” in line with the recommended practice.
I wrote about the restrictions. Now let me tell you more about the advantages of the discussed solutions. You will probably agree with me that the ability to add logic inside functional components previously reserved for class components is not something that would justify the confusion caused by adding hooks. Although the minification of functions, which are functional components, is much more effective than the minification of classes in JavaScript. This is probably not what guided the authors of the solution which are hooks. React Hooks is primarily a great help when writing reusable code. They eliminate some usage of advanced patterns such as Higher-Order Component (HOC) and Render Props used in order to increase the reusability of the code, which in practice allows to reduce the number of abstraction layers. We have the ability to prepare custom hooks that can be used inside independent components, which is a very big advantage of the React Hooks solution.
Read more: React Native vs. native iOS with Swift: Clash of the Titans
The logic that we currently place inside hooks, we added to the component lifecycle methods before introducing them. Very often, such logic required some operations to be performed during component initialization, as well as re-rendering or deleting it. This can be seen in the code snippet below.
class Home extends React.Component {
state = { opened: false };
componentDidMount() {
this.open(this.props.hasKey);
}
componentWillUnmount() {
if (this.state.opened) {
this.close();
}
}
open(hasKey) {
// ...
this.setState({ openned: hasKey });
}
close() {
// ...
}
componentDidUpdate(prevProps) {
if (this.props.hasKey) {
this.open(this.props.hasKey);
}
}
render() {
// ...
}
}
Although these operations were logically related to each other, they had to be broken down and placed in different component lifecycle methods. Now, thanks to useEffect, such operations, which create more logic, can be gathered in one place.
export default function Home({ hasKey }) {
const [isOpenned, setAsOpenned] = useState(false);
const open = useCallback((hasKey) => {
setAsOpenned(hasKey);
}, [setAsOpenned]);
const close = useCallback(async () => {
if (isOpenned) {
// ...
}
}, [isOpenned]);
useEffect(() => {
open(hasKey);
return () => {
close();
};
}, [hasKey, close, open]);
return (
// ...
);
}
Let’s take a look at the state management offered by React before implementing hooks. In the classical approach, a component’s local state object often contained unrelated data. In the React Hooks approach, the useState function can be called multiple times inside the same component, which allows you to split the state into smaller pieces of data that constitute a logical whole. In addition, it is currently possible to separate state fragments into custom hooks written by us. Additionally, the setState method used to update the component’s local state modifies the state value fragmentarily. The following example would only overwrite the value assigned to the key surname which can be confusing for a programmer new to React.
state = { surname: "Nowak", address: "ul. Polna 1" };
componentDidMount() {
this.setState({ surname: "Kowalski" });
}
In my opinion, updateStatePartially would be a more appropriate name. Hook useState, however, overwrites the full value of the local state of the component each time, and we define the name of the function that updates this value.
Conclusions
The React Hooks solution undoubtedly has many advantages, but at the moment it does not cover all the possibilities offered by class components. Hook equivalents are not available for lifecycle methods such as getSnapshotBeforeUpdate, componentDidCatch, getDerivedStateFromError. The last two allow you to catch JavaScript errors, log them in and display the emergency user interface, which is a very useful feature. Hooks, as I wrote above, introduce a number of facilities, but there are also exceptions. In the case of function components, if the arguments called props change between their invocation and execution, the asynchronous function will not be aware of these modifications. We must consciously use the reference in this case. Hook’s carry some solutions that I believe may be counterintuitive for programmers used to class components. For example, if we want to initialize the state in a class component, we add the appropriate logic in the constructor or the class body and we know that the initialization will be done once. For function components, remember to pass the function without arguments as a parameter for useState, similar to the example below.
const [data, setData] = useState(() => calculateData());
In the case of useEffect, if we want the logic to be executed once, we must remember to send the empty array in the form of the second argument. If we forget about it, we can reduce application performance through redundant operations
In my opinion, React Hooks are a valuable solution that should not be overlooked. I hope that I have managed to present my point of view in an objective way addressing both the advantages and the disadvantages of the new version of the React library. However, I am aware that there might be much more features to be discussed further.
You may like to read:
React Native vs. native iOS with Swift: Clash of the Titans
What is the difference between ReactJS and NodeJS?
Why use React.js for Web Development?