This tutorial is out of date and no longer maintained.
Last week on the code challenge we set out to refactor some class components in a create-react-app
project to functional components using React hooks.
In this post, we shall complete the challenge. On completion of this challenge, you should be able to completely write React components having state and lifecycle methods using JavaScript functions.
Are you yet to complete the challenge, just fork this CodeSandbox and get started. You can look through these posts by Chris and Peter for guidance.
You can also look through Twitter for the hashtag #ScotchCodeChallenge
to share some of the amazing entries, same as the comment section of the challenge post.
We were provided with a simple React app on CodeSandbox with only the required dependencies installed. In the React app, we have 6 individual components all written as class components which required conversion to functional components.
Note: To use React Hooks, you must be running React from version 16.7.
React introduced the use of React hooks in the alpha version of React 16.7. How do hooks work? Two new APIs were exposed to handle state and lifecycle methods (which are the core components of class functions). These APIs are useState
and useEffect
which handles state and lifecycle methods effectively.
Next, we shall explore the usage of these two hooks - Hooks help you utilize the features of class components in functional components.
Without looking through the process of rewriting the components you can go straight to the solution CodeSandbox here:
https://codesandbox.io/s/mq297nro3x
Looking at the individual components from first to last in src/components
we have:
This was previously written as a class component with a default export.
import React, { Component } from "react";
class One extends Component {
state = {
count: 0
};
increase = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div style={{ marginBottom: "50px" }}>
<h2>Challenge 1</h2>
<p>Count is: {this.state.count}</p>
<button onClick={this.increase}>Increase Count!</button>
</div>
);
}
}
export default One;
As can be seen, we have a state variable present count
, which is used to build a simple counter. Using the useState
hook we shall rewrite the component to a function.
import React, { useState } from "react";
const One = () => {
const [count, setCount] = useState(0);
return (
<div style={{ marginBottom: "50px" }}>
<h2>Challenge 1</h2>
<p>Count is: {count}</p>
<button onClick={() => setCount(count + 1)}>Increase Count!</button>
</div>
);
};
export default One;
We can see this is much cleaner and condensed. Here we simply used destructured the useState
hook and assigned the values of count
and setCount
to the state value and the method to change this value respectively.
The initial value of the state variable is passed to useState
and hence, the value of count
and actions of setCount
can then be used in the rendered component.
Similarly, from One.js
we have a state variable only this time it’s a string.
import React, { Component } from "react";
class Two extends Component {
state = {
activeUser: "Chris"
};
changeUser = () => {
this.setState({ activeUser: "Bolingo!" });
};
render() {
return (
<div style={{ marginBottom: "50px" }}>
<h2>Challenge 2</h2>
<p>Active User is: {this.state.activeUser}</p>
<button onClick={this.changeUser}>Change Me!</button>
</div>
);
}
}
export default Two;
In a similar fashion we utilize useState
to include the state variable in a functional component as seen below:
import React, { useState } from "react";
const Two = () => {
const [activeUser, changeUser] = useState("Chris");
const newName = () => changeUser("Bolingoli!");
return (
<div style={{ marginBottom: "50px" }}>
<h2>Challenge 2</h2>
<p>Active User is: {activeUser}</p>
<button onClick={newName}>Change Me!</button>
</div>
);
};
export default Two;
In this component, we created a new function to handle the name change using the changeUser
function destructured from useState
.
How about we look at multiple state values this time next.
In this component, we have 3 state variables that would change in the component. useState
can be destructured multiple times for each individual variable. With this, we can handle all state variables at once, and on render, they are invoked in the order in which they are listed.
import React, { Component } from "react";
class Three extends Component {
state = {
year: 1995,
type: "Mercedes",
used: true
};
swapCar = () => {
this.setState({
year: 2018,
type: "BMW",
used: false
});
};
render() {
return (
<div style={{ marginBottom: "50px" }}>
<h2>Challenge 3</h2>
<h3>Car Spec is:</h3>
<ul>
<li>{this.state.type}</li>
<li>{this.state.year}</li>
<li>{this.state.used ? "Used Car" : "Brand New!"}</li>
</ul>
<button onClick={this.swapCar}>Swap Car!</button>
</div>
);
}
}
export default Three;
Refactoring this we have:
import React, { useState } from "react";
function Three() {
const [year, changeYear] = useState(1995);
const [type, changeType] = useState("Mercedes");
const [used, changeCondition] = useState(true);
const swapCar = () => {
changeYear(2018);
changeType("BMW");
changeCondition(false);
};
return (
<div style={{ marginBottom: "50px" }}>
<h2>Challenge 3</h2>
<h3>Car Spec is:</h3>
<ul>
<li>{type}</li>
<li>{year}</li>
<li>{used ? "Used Car" : "Brand New!"}</li>
</ul>
<button onClick={swapCar}>Swap Car!</button>
</div>
);
}
export default Three;
Similarly, we created a function swapCar
to handle all changes.
In this component, componentDidMount
was used to update the value of a state variable, 5 seconds after the component mounts. Here we would require the useEffect
hook to handle the lifecycle method. Originally we had:
import React, { Component } from "react";
class Four extends Component {
state = {
message: "What's happening this week?"
};
componentDidMount() {
setTimeout(() => {
this.setState({ message: "React project due" });
}, 5000);
}
render() {
return (
<div style={{ marginBottom: "50px" }}>
<h2>Challenge 4</h2>
<p>Status: {this.state.message}</p>
</div>
);
}
}
export default Four;
With useEffect
and useState
we have:
import React, { useState, useEffect } from "react";
const Four = () => {
const [message, newessage] = useState("What's happening this week?");
useEffect(() => {
setTimeout(() => {
newessage("React project due");
}, 5000);
}, []);
return (
<div style={{ marginBottom: "50px" }}>
<h2>Challenge 4</h2>
<p>Status: {message}</p>
</div>
);
};
export default Four;
In useEffect
, we used the setTimeout
function to delay the execution of the newMessage
method which updates the state. Notice the empty array passed as a second parameter passed to useEffect
, this allows the function to run only once (on the first render). Functions in useEffect
update for every time the value of the array or a value in the array changes. This should give you insights into how to handle updates in the component.
Here we are required to convert a class component that conditionally renders another component using ternary operators, to a functional component.
import React, { Component } from "react";
import Little from "./Little";
class Five extends Component {
state = {
showText: true
};
showLittle = () => {
this.setState({ showText: !this.state.showText });
};
render() {
return (
<div style={{ marginBottom: "50px" }}>
<h2>Challenge 5</h2>
<h3>Here below lies little text in a box</h3>
<button onClick={this.showLittle}>Click to toggle Little</button>
{this.state.showText ? <Little /> : ""}
</div>
);
}
}
export default Five;
Converting to a functional component we have:
import React, { useState } from "react";
import Little from "./Little";
const Five = () => {
const [showText, toggleShowText] = useState(true);
const showLittle = () => {
toggleShowText(!showText);
};
return (
<div style={{ marginBottom: "50px" }}>
<h2>Challenge 5</h2>
<h3>Here below lies little text in a box</h3>
<button onClick={showLittle}>Click to toggle Little</button>
{showText ? <Little /> : ""}
</div>
);
};
export default Five;
useState
was employed once more to create and update a state value. Also, a component Little.js
was imported and conditionally rendered once the state is toggled using the created button. Let’s look at the last component and its behavior on toggle.
We have a simple class component that utilizes the componentWillUnmount
method to alert users. This is just a simple depiction of the method and other actions can be carried out in the method.
import React, { Component } from "react";
class Little extends Component {
componentWillUnmount() {
alert("Goodbye!!");
}
render() {
return (
<div style={{ marginBottom: "50px", border: "1px solid black" }}>
<h5> Hi I'm Little and its nice to meet you!!!</h5>
</div>
);
}
}
export default Little;
Converting this to a functional component and retaining the features of the lifecycle method we have:
import React, { useEffect } from "react";
const Little = () => {
useEffect(() => {
return () => {
alert("Goodbye!!");
};
});
return (
<div style={{ marginBottom: "50px", border: "1px solid black" }}>
<h5> Hi I'm Little and its nice to meet you!!!</h5>
</div>
);
};
export default Little;
To use the componentWillUnmount
method, simply return a function in the useEffect
hook. Now once we click the button in Five.js
to toggle Little.js
the function in the useEffect
hook of Little.js
will be called before the component unmounts.
In this post, we saw the usage of React Hooks to convert class components to functional components yet retaining the features like state and lifecycle in these components. Try out your hands on other lifecycle methods as well as using both useEffect
and useState
in a single component.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.