Post

Convert Class Components to Functional Components in a React Project (Solution to Code Challenge #14)

Draft updated on Invalid Date
Default avatar

By William Imoh

Convert Class Components to Functional Components in a React Project (Solution to Code Challenge #14)

This tutorial is out of date and no longer maintained.

Introduction

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.

Note: 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 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:

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 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.

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 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.

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: "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.

Five.js

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.

Little.js

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.

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.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
William Imoh

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


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!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel