In this tutorial, we started with an existing React-app with which we can calculate how much range the Tesla has under different circumstances. The range of a battery depends on the speed, the outside temperature, the climate and the wheel size.
In order to learn React hooks more thoroughly I converted this existing React-app from React Class Components to one with just React Functional Components. That is the goal of this tutorial!
As a starting point for the tutorial, clone this Github repository.
Then, move to the reactjs-app directory:
Project Structure
The project we are working on has the same structure as the aforementioned image and shows the components that make up this application. The index.js is the entry point of the application. App.js is the entry component of the application. The outer edge of the figure above shows the App.js component.
Breaking Down the UI
Almost all React applications consist of a composition of components. This application consists of an entry App.js component with the TeslaBattery as a child component. And the TeslaBattery component contains the following child components:
-
TeslaCar: for rendering the TeslaCar image with wheel animation.
-
TeslaStats: for rendering the maximum battery range per Tesla model. This concerns the models: 60, 60D, 75, 75D, 90D, and P100D.
-
TeslaCounter: for manually controlling the speed and the outside temperature.
-
TeslaClimate: this changes the heating to air conditioning when the outside temperature is more than 20 degrees.
-
TeslaWheels: for manually adjusting the wheel size from 19 inches to 20 inches and vice versa.
These child components of the TeslaBattery are already React Functional Components.
But the TeslaBattery is still a React Class Component. In this tutorial, we are going to convert this component to a React Functional Component with hooks in 8 steps.
But What Is a Hook?
Hooks are functions that let you “hook into” React state and lifecycle features from functional components. Hooks don’t work inside classes. So our first step is to change the class to a function with hooks.
The components tree of the application is as follows:
Steps to Convert the TeslaBattery Component to a Functional Component
First, open the source file TeslaBattery.js in your IDE and follow these steps:
1. Change the Class to a Function
Change
To
2. Remove the Render Method
Remove the render method, but keep everything after, including the return. Make this the last statement in your function.
From
To
You see that we also removed this.state in the first line. This is because we handle state in a different way (see step 5).
3. Convert All Cethods to Functions
Class methods won’t work inside a function, so let’s convert them all to functions (closures).
From
To
Note: Do this also for all other methods!
4. Remove this.state Throughout the Component
The this.state variable in your function isn’t useful anymore. Remove the references to it throughout your render and functions. In step 6, we use the useState hook for this.
5. Remove References to this
The this variable in your function isn’t useful any more. Remove the references to it throughout your render and functions.
6. Remove the Constructor
Simply removing the constructor is a little tricky, so I’l break it down further.
1. Remove Event Handler Bindings
We don’t need to bind event handlers any more with functional components. So if you were doing this;
You can simply remove these lines (What a gross, overly verbose syntax anyway).
2. useState Hook
Instead of
Use the useState hook
What does calling useState do? It declares a “state variable”. Our variables are called carstats
and config
, but we could call it anything else, like ‘banana’. This is a way to “preserve” some values between the function calls — useState is a new way to use the exact same capabilities that this.state provides in a class. Normally, variables “disappear” when the function exits but state variables are preserved by React.
What do we pass to useState as an argument? The only argument to the useState()
hook is the initial state. Unlike with classes, the state doesn’t have to be an object. We can keep a number or a string if that’s all we need. In our example, we pass an empty array as initial state for our variable carstats
. And we pass an object with default values as initial state for our variable config
.
What does useState return? It returns a pair of values: the current state and a function that updates it. This is why we write const [carstats, setCarstats] = useState([])
. This is similar to this.state.carstats
and this.setState()
in a class, except you get them in a pair.
So, do not use this.setState()
in Functional Component. this.setState()
works only in Class Components to update the state of variables. See step 7.
Finally import your hook:
7. Replace this.setState
this.setState
obviously doesn’t exist any more in our function component. Instead, we need to replace each of our setState calls with the relevant state variable setter.
Replace first the this.setState
for carstats
:
With the setCarstats – setter
Attention! Call React hooks always at the Top Level of your function.
AND
Replace the this.setState
for config
:
Note: You see in this example how this.setState
accept a callback (() => {this.statsUpdate()
) that would run after the state-object (i.e. the config-object) is updated? Well our useState
updater function does no such thing. Instead, we have to use the useEffect
hook. This is explained in the next section.
Replace this.setState with this setConfig
– setter:
Do this also for the following event handlers for setting the state of the config-object:
handleChangeClimate(size)
.handleChangeWheels(size)
.
8. Replace Lifecycle Methods With the useEffect Hooks
First import your useEffect hook
Replace ComponentDidMount()
Instead of using the componentDidMount lifecycle method, use the useEffect
hook with the dependency array filled: [config] !
Replace
With
The Effect Hook lets you perform side effects in Functional Components. The side effect here is executing the statsUpdate()
function.
The dependency array [config] given in parameters lets you fire the side effect only when the config-object in the array is changed. This way, you can easily create lifecycle methods like componentDidMount
and componentDidUpdate
via the Effect hook.
What does useEffect
do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we call the statsUpdate()
function, but we could also perform data fetching or call some other imperative API.
Does useEffect
run after every render? Yes! By default, it runs both after the first render and after every update. Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
Motivation of Using Hooks
With Hooks, you can extract stateful logic from a component so it can be tested independently and reused. Hooks allow you to reuse stateful logic without changing your component hierarchy. This makes it easy to share Hooks among many components or with the community.
And Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods.
Conclusion
In order to learn this more thoroughly I converted an existing React-app from class components to one with just functional components using just the hooks in this article. The result was dramatically cleaner and easier to read code.
I can’t see any reason why I would not use hooks exclusively in the future. I look forward to creating custom hooks and using some of the more advanced features that hooks have to offer.