Junior v. Senior React Code: Class & Function Components

Junior v. Senior React Code: Class & Function Components

This post is one in a series in which we take React code that a less-experienced developer would write and improve it so that it resembles more what a professional would write.

Today we’re covering transitioning from class to function components.

The junior code

(If you've already read the other posts in the series, you can skip this section.)

Below is the component that we’re going to refactor. Here is a link to an interactive version.

import React, { Component } from "react";

export default class ItemList extends Component {
  state = { items: [], category: null };
  componentDidMount() {
    this.getData(true).then(({ urls, category }) => {
      this.setState({ items: urls, category });
    });
  }

  loadMore = () => {
    this.getData(false).then(({ urls }) => {
      this.setState({ items: this.state.items.concat(urls) });
    });
  };

  getData = (isInitialLoad) => {
    // This function loads iamges of cats, and if it is initial load, then it determines the
    // first category so that it keeps loading the same category
    const limit = isInitialLoad ? 20 : 10;
    return fetch(
      `https://api.thecatapi.com/v1/images/search?limit=${limit}${
        isInitialLoad ? "" : `&category_ids=${this.state.category}`
      }`
    )
      .then((res) => res.json())
      .then((responses) => {
        let urls = responses.map((data) => data.url);
        if (isInitialLoad) {
          const categories = responses.reduce((acc, resp) => {
            if (resp.categories) {
              acc = acc.concat(resp.categories);
            }
            return acc;
          }, []);
          const category = categories[0].id;
          return { urls, category };
        }
        return { urls };
      });
  };

  render() {
    return (
      <div>
        {this.state.items.map((item, ind) => (
          <img
            src={item}
            width={400}
            height={300}
            key={ind}
            alt={`pic-${ind}`}
          ></img>
        ))}
        <button onClick={this.loadMore}>Load more</button>
      </div>
    );
  }
}

Below is the refactored version of the same code.

Click here for a live version.

import React, { useState, useEffect, Fragment } from "react";

const baseURL = "https://api.thecatapi.com/v1/images/search";
const initialLoadNumber = 20;
const normalLoadNumber = 10;

export const ItemList = () => {
  const [urls, setUrls] = useState([]);
  const [category, setCategory] = useState(null);
  const [isInitialLoad, setIsInitialLoad] = useState(true);

  useEffect(() => {
    if (isInitialLoad) {
      setIsInitialLoad(false);
      loadInitialRandomCategoryImages().then(({ urls, category }) => {
        setUrls(urls);
        setCategory(category);
      });
    }
  }, [isInitialLoad]);

  const loadInitialRandomCategoryImages = () => {
    return fetch(`${baseURL}?limit=${initialLoadNumber}`)
      .then((res) => res.json())
      .then((responses) => {
        const urls = responses.map((data) => data.url);
        const imgWithCategory = responses.find((resp) => resp.categories);
        const category = imgWithCategory
          ? imgWithCategory.categories[0].id
          : null;
        return { urls, category };
      });
  };

  const loadMoreSameCategoryImages = () => {
    return fetch(
      `${baseURL}?limit=${normalLoadNumber}&category_ids=${category}`
    )
      .then((res) => res.json())
      .then((responses) => {
        const urls = responses.map((data) => data.url);
        return { urls };
      });
  };

  const loadMore = () => {
    loadMoreSameCategoryImages().then(({ urls: newUrls }) => {
      setUrls(urls.concat(newUrls));
    });
  };

  return (
    <Fragment>
      {urls.map((item, ind) => (
        <img
          src={item}
          width={400}
          height={300}
          key={ind}
          alt={`pic-${ind}`}
        ></img>
      ))}
      <button onClick={loadMore}>Load more</button>
    </Fragment>
  );
};

export default ItemList;

The main thing the component does is fetch cat pictures and displays them. At the bottom is a button that when clicked will load more photos. There is just one somewhat complicated feature. When the user clicks the “Load more” button, we only want to show photos in the same category as the first photo that was shown when the component initially rendered, for example "cats in hats" or "kittens". This is a strange design choice, but let’s just pretend that the designer on this project has some theory about how that feature will increase engagement so we have to implement it.

Transitioning from class to function components

There are 2 ways to create a React component: use a function or use a class.
Early on in React, the best practice was to use a function component whenever possible. Function components had 3 advantages over class components:

  1. When function components are compiled using Babel, they are smaller. This decreases your bundle size and improves the performance of your app, especially the initial load time (source).
  2. Function components are easier to read, understand, and work with. They are just simple functions. Simpler code is just generally better.
  3. A function component made clear that it was stateless. A key design principle of building React apps is that you distinguish between presentational and stateful components. In a nutshell, presentational components handle appearance. Stateful components handle the state. In practice, it’s hard to maintain this distinction. Stateful components often end up having some styling too. But the more you can maintain this distinction, the easier it is to change the appearance of your app. You can swap out presentational components and not have to worry about how all your state logic will be impacted. It used to be impossible to have an internal state in function components, which guaranteed that it was presentational. In programming, like in life, guarantees make your life easier.

If you needed to add a state or you needed lifecycle hooks, you would use a class component.  If you’ve worked with React, you’ve almost certainly seen lifecycle hooks in class components. They are the methods that are named things like componentWillMount and componentDidMount. They are how you manage updates to the component at different points in the component’s lifecycle. For instance, componentWillMount executes before the component renders and componentDidMount executes after the component renders.

Until React version 16.8 (released February 2019) you could only use lifecycle hooks inside a class component because they are methods on the component class that comes with React. Therefore, if you needed to use them, you needed to “extend” the React component class to create your component. (If this stuff about classes and extending them doesn’t make sense, you should probably read up on object-oriented programming. This is essential programming knowledge. Here is a good place to start).

However, this whole function v. class component situation is kind of irrelevant since hooks were introduced. Hooks allow you to have an internal state and give you lifecycle hooks inside of function components. In other words, everything you used to only be able to do in class components you can now do in function components.

Image showing a React logo being hooked like a fish.
Everyone is hooked on React hooks! (I'm also hooked on bad puns!)

So is there even a point to class components anymore? Sure. You can still use them. The official React has said that, "there are no plans to remove classes from React." We would suggest trying hooks out and seeing how you like them. They seem like the future of React, but right now it's hard to say for sure.

Hooks are powerful and they do several different things. Covering them thoroughly would take a couple of blog posts. Here we’re just going to cover one of the most-commonly used flavors of hooks: state hooks. Below is one of the state hooks we use in the new code:

const [urls, setUrls] = useState([]);

The useState function gives you 2 things: An array called urls that is part of the component’s state. To use urls, just use it. No more of that this.state.urls stuff. The second thing you get is a function called setUrls. You use that function to update urls.

It’s that simple to get started with hooks. We also introduce another kind of hook called an effect hook in the aptly named useEffect function. We’ll let you explore that further on your own if you’re interested.


If you liked this post, we recommend you subscribe to the Antcode blog (see below) and follow us on Twitter at @o_nagen and @mkinoshita12.

If you want to get reviews on your code and learn from reviews on other people’s code, we recommend that you check out what we’re building at Antcode and join our code review Slack group.

Happy coding!

Show Comments