What are React Hooks? How are they used? What are the pros and cons of React Hooks compared to the Lifecycle Methods found in class-based components? Today we're answering these questions and more!
"Should I use functional React components or class-based components?" This is a question that many developers ask at some point, either at the beginning of a project when defining the architecture, or even after the project is in progress when challenges arise.
For a long time, the class-based approach was widely used, as it was often the only option given to the developer. This is primarily because it provides easy ways to manipulate component life cycle methods and state management. However, everything changed with the advent of React Hooks.
What Are React Hooks?
According to the official documentation, Hooks were introduced in React 16.8 to enable the use of state and other React features without writing a class. They are basically functions that allow you to manage the state and lifecycle features from a function component.
At this point you may be wondering, “So do I need to rewrite all of my components that are class-based?” And the direct answer is, "No, you don't." The good news is that components are handled independently, so there is no problem with having a mix of class-based components and functional components. In fact, it's even possible for different component types (functional and class-based) to be parented to each other.
But how do you use these React Hooks? To answer that question, let’s take a look at two main hooks -- the useState Hook and the useEffect Hook -- and how they replace lifecycle methods.
The useState Hook
When we use classes, we have the state of the component (“this.state”, which is an object). This is manipulated by the method setState, in which we can pass as an argument the property of the state we want to modify.
But with the arrival of useState, this is no longer necessary. We can create variables for the state, and we can also define its initial value and the specific function to edit that state property, which may or may not be an object.
The code below shows an example of how useState works. In this case, we are defining three properties, "name," "age," and "company". Note that though their values can be modified, they are defined as "const" with the name of the property and its updating function. The value in parentheses is the initial value defined for that property.
To modify the values of these state properties, we simply call the defined function to update the property, passing the new value as an argument. In the example below, we changed the name to "Stephen" and the age to “25".
One thing to note is that useState does not automatically merge objects. To accomplish this, we can use the function updater form with object spread syntax. In this case, we are defining a property “Person” as an object and updating only the “company”.
We can observe that useState is a different way to use the capabilities that “this.state” provides in a class component. In other words, the “state” can be replicated using the “useState" function.
The useEffect Hook
What about the side effects in function components? Well, these can be performed using the useEffect hook. They are particularly useful when we need to run some code after the DOM has been updated, e.g. when we have a network request or a user logging in to the portal.
UseEffect allows us to access any prop from the effect. The useEffect will be triggered once the component is rendered, and we may pass an argument that will be watched and will trigger the function every time its value is updated.
If we do not pass an argument, the useEffect will only be triggered when the component is mounted. Let’s take a look at this exemple:
In this case, we have a property of the state called “someData” with the initial value set as null. We want that property to be updated with some data from the database (DB). To accomplish this, we are using useEffect to call a function that will actually retrieve the data from the DB and then update someData with the result. Note that the square brackets are empty (second useEffect parameter), which will cause the method to be called only at the component initialization.
But what if we are rendering that data to the page? Well, we probably want to always show the most recent data. To accomplish this, useEffect can be used with the property to be watched inside the square brackets:
This way, whenever the someData property has its value updated, this useEffect will be called and will also update the data to be displayed, using the setShownData method.
It is possible to have as many useEffect methods as needed within the component, each being activated when a specific property that is desired is updated. It is also possible to pass more than one argument to be watched in the useEffect:
In this example, two arguments are being watched (firstArg and secondArg), and this useEffect will be called once the component is mounted, as well as every time firstArg or secondArg is updated. This replaces the ComponentDidMount and ComponentDidUpdate from React classes.
It's also possible to replace componentWillUnmount with useEffect as well. To do so, you'll need to use a return inside the useEffect, which will be triggered when the component is about to be unmounted:
Please note that there are other React Hooks like “useContext”, “useReducer”, and “useCallback”, among others, that we have not covered in this blog.
Before React Hooks were created, functional components were great solutions for simply taking certain properties and rendering them to the page. When lifecycle methods and other functions were required, or when the state needed to be updated, classes were used.
But React Hooks changed the game. They use the array restructuring syntax to allow us to give different names to sate variables with "useState" and to replace lifecycle methods with "useEffect".
Despite this, some developers still prefer to use classes because when we use functional components, event handling functions are redefined per render, which can cause performance issues. While this is true, it's important to consider whether the components are really rendering such a large amount of volume at a speed that would really be problematic, and then analyze the trade-offs and benefits of using React Hooks.
Gustavo Mendonça is a Software Engineer at Avenue Code. With a master's degree in mathematical and computational modeling from CEFET-MG and a bachelor's degree in computer engineering from the same institution, Gustavo is fascinated by technology and its evolution.