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

Celý článek

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.

Nahoru
Tento web používá k poskytování služeb a analýze návštěvnosti soubory cookie. Používáním tohoto webu s tímto souhlasíte. Další informace