From class component to hooks (Cheatsheet included)
You are comfortable with class components and you are wondering if you should learn React hooks (short answer: yes, you should).
Don’t worry about losing your job if you don’t learn hooks because class components won’t be deprecated and there is no need to update all codebases to hooks.
However, hooks are a thing. They may look over-hyped, but they are important specially when starting something from scratch.
I know that feeling when you’re comfortable with a technology and you’ve to learn a new toy (I had to learn Node when I was used to Ruby on Rails 😭)
In this post we are going to use what you already know to kick-off your hooks journey. I know that your brain works well with classes, so lets take this advantage!
Refactoring a simple component
One of the first things we learn in React (classes) is the component lifecycle: constructor
, componentDidMount
, componentWillUnmoun
and render
.
Let’s use them and create a “you have been X seconds here” example.
This app will show how long have you been in the page. To build this, we will:
- When the component is initialized, we initialize a state with
counter = 0
. We do this in theconstructor
. - After the component is mounted, we start a timer interval that increases the counter by one every 1000 milliseconds (1 second). We do this in
componentDidMount
. - Before the component is unmounted, we clear the timeout. We do this in
componentWillUnmount
.
Here is the implementation of this app:
import React from "react";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
}
componentDidMount() {
this.intervalID = setInterval(() => {
this.setState(state => ({
counter: state.counter + 1
}));
}, 1000);
}
componentWillUnmount() {
clearInterval(this.intervalID);
}
render() {
const { counter } = this.state;
return (
<div style={{ textAlign: "center" }}>
<h1>Hey</h1>
<div>You have been there for {counter} seconds.</div>
</div>
);
}
}
I hope you don’t see anything strange here. It uses class component with the classic component lifecycle methods.
Now I’ll show you how to refactor it to hooks.
Step 1: transform your class component into a function component
In other words:
- Replace
class
forfunction
- Remove
extends React.Component
- Remove everything inside except the
render
code
In our case:
import React from "react";
export default function App() {
const { counter } = this.state;
return (
<div style={{ textAlign: "center" }}>
<h1>Hey</h1>
<div>You have been there for {counter} seconds.</div>
</div>
);
}
This is not working yet. We have to modify it a bit…
Step 2: state management with useState
You know how to use state in class components:
- Initialize
state
in theconstructor
. - Update it using
setState
method which triggers a new render. - Read state properties from
this.state
In functional components, the state
works similar:
- it keeps values between renders
- updating it triggers a new render
However, in function components, you don’t have a constructor
and you don’t have instance methods such as setState
.
So the mechanics here are a bit different. To use state
in a functional component, you have to use a hook, which is a function that gives superpowers to your functional components.
The hook that brings state
superpower to our function components is called useState hook
export default function FunctionalComponentWithState() {
const someState = React.useState(initialValue); // <-- state superpower!!
// ...
}
By calling useState, you are creating a single piece of state associated to the component. It is similar to initializing the state in the constructor of a class component.
useState
returns two things:
- the current value of the state variable
- a function to update it:
function ComponentWithState() {
const [currentValue, updateValue] = React.useState(initialValue);
}
Well, technically
useState
returns an array of size two where the first element is the current value and the second element is a function to update it.
Lets go back to the example. We need to store the counter, so instead of
const { counter } = this.state;
We should use:
const [counter, setCounter] = useState(0); // 0 is the initial value
Now our App
code should look like this:
import React, { useState } from "react";
export default function App() {
const [counter, setCounter] = useState(0);
return (
<div style={{ textAlign: "center" }}>
<h1>Hey</h1>
<div>You have been there for {counter} seconds.</div>
</div>
);
}
This works! But we are not done yet because we are not counting every second.
Step 3: implement componentDidMount with useEffect hook
In function components, to execute code after the component has been rendered you have to use another superpower (hook) called useEffect hook.
The useEffect
hook takes a function which will be invoked after render. Very similar to componentDidMount
:
import React, { useState, useEffect } from "react";
export default function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
// "componentDidMount" logic should be here
}, []);
return (
<div style={{ textAlign: "center" }}>
<h1>Hey</h1>
<div>You have been there for {counter} seconds.</div>
</div>
);
}
componentDidMount
logicAfter render
(in our useEffect
) we have to:
- start the timer (
setTimeout
). - update the counter on “every tick”.
Instead of using setState
we should use setCounter
. The state update logic:
this.setState(state => ({
counter: state.counter + 1
}));
is replaced with:
setCounter(value => value + 1);
When setCounter
is called, it will trigger a new render, similar to calling this.setState
in the class component.
Now our App looks like this:
import React, { useState, useEffect } from "react";
export default function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
setInterval(() => {
setCounter(oldCounterValue => oldCounterValue + 1)
}, 1000);
}, []);
return (
<div style={{ textAlign: "center" }}>
<h1>Hey</h1>
<div>You have been there for {counter} seconds.</div>
</div>
);
}
Which does almost everything!
I said before that the function you pass to useEffect is invoked after render. That is different than after mount. The empty array is used to tell React to invoke only once (on mount!)
useEffect(() => {
setInterval(() => {
setCounter(oldCounterValue => oldCounterValue + 1)
}, 1000);
}, []); // <--- HERE
Step 4: implement componentWillUnmount
with useEffect
’s cleanup function
useEffect
can return a function that cleans up effects from the previous render, which can be used to remove subscriptions (for example, to call clearInterval
).
If your effect runs only on “mount” (like our component), the cleanup logic will run only on unmount. Our updated useEffect
looks like this:
useEffect(() => {
cont intervalId = setInterval(() => {
setCounter(oldCounterValue => oldCounterValue + 1)
}, 1000);
return () => { // <--- returns a "cleanup" function
// "on unmount"
clearInterval(intervalId);
}
}, []);
The final code
And the final code:
import React, { useState, useEffect } from "react";
export default function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCounter(oldCounterValue => oldCounterValue + 1)
}, 1000);
return () => clearInterval(intervalId);
}, []);
return (
<div style={{ textAlign: "center" }}>
<h1>Hey</h1>
<div>You have been there for {counter} seconds.</div>
</div>
);
}
Recap
useState instead of this.state = { … }
useState
asks for an initial value, and returns the current value and a function to update it. All in a single line of code:
const [currentValue, setValue] = useState(initialValue);
Calling setValue
will trigger a new render, just like using this.setState
.
useEffect instead of componentDidMount / componentWillUnmount
useEffect
takes a function which is invoked after render. This is generally used to run code with side effects such as HTTP requests, subscriptions, etc. Similar to what you do in componentDidMount
.
You can tell react to run cleanup logic (removing a subscription for example) that you added in your effect by making your effect return a function.
useEffect(() => {
// on mount: do something
return () => {
// on unmount: do something
}
}, []); // <--- empty array = tell react to run it once
useEffect
that imitates componentDidMount
and componentWillUnmount
Too much to remember? Don’t worry, I made a Class components to Hooks Cheatsheet you can use 🎁 💝
Get it below 👇