Convert Class Components to Functional Components in a React Project (Solution to Code Challenge #14)
Publikováno: 14.12.2018
Last week on the code challenge we set out to refactor some class components in a create-react-app proj...
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.
The Challenge
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.
To use React Hooks, you must be running React from version 16.7
Brief
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.
The Solution
Without looking through the process of re-writing the components you can go straight the solution codesandbox here: https://codesandbox.io/s/mq297nro3x
https://codesandbox.io/s/mq297nro3x
Looking at the individual components from first to last in src/components
we have:
One.js
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 re-write 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.
Two.js
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.
Three.js
In this component, we have 3 state variables which would change in the component. useState
can be destructured multiple times for each individual variable. With this, we can handle all state variable at once, and on render, they are invoked in the order 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.
Four.js
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: "I only know it's gon be lit!!" });
}, 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("I only know it's gon be lit!!");
}, 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 everytime the value of the array or a value in the array changes. This should give you insights on how to handle updates in the component.
Five.js
Here we are required to convert a class component which 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.
Little.js
We have a simple class component which 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.
Conclusion
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.