Bug Hunt

Find and fix all the bugs in an existing component.


This article will deviate slightly from the format of most articles. Instead of designing and writing a component, you will identify bugs in a component that has already been written.

Environment

Use the environment you are most comfortable with. I recommend using create-react-app to create a local version of the project so that you can inspect the DOM easily from your browser when debugging. Alternatively, you can work from a blank React template in CodeSandbox. Starter code is provided after the problem specification.

Component Description

The component provided in the code section below contains two bugs. In its initial state, the component should display five buttons in a row (each of which opens an image) along with a line of text below the buttons that indicates how many of the images are currently open. When the user clicks an image, the image should close and the button should retake its place. The correct starting state is pictured below:

The goal component in its starting state
The goal component in its starting state

The intended component after the first and fourth buttons are clicked:

The goal component with its first and fourth buttons clicked
The goal component with its first and fourth buttons clicked

Buggy Code

Below is the buggy code. It can be fixed with just two small syntax changes.

import React, { useState } from "react";

const BUNNY =
  "https://images.pexels.com/photos/326012/pexels-photo-326012.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2";

function App() {
  return <BuggyButtons />;
}

const BuggyButtons = () => {
  const [openCount, setOpenCount] = useState(0);
  const IDs = [1, 2, 3, 4, 5];
  const verb = openCount == 1 ? "is " : "are ";
  const noun = openCount == 1 ? "bunny." : "bunnies.";

  return (
    <>
      <div
        style={{
          display: "flex",
          flexDirection: "column",
        }}
      >
        <div
          style={{ display: "flex", justifyContent: "center", marginTop: 50 }}
        >
          {IDs.map((item) => {
            <div
              style={{
                marginRight: 10,
              }}
              id={item}
            >
              <BuggyButton openCount={openCount} setOpenCount={setOpenCount} />
            </div>;
          })}
        </div>
        <div style={{ alignSelf: "center", marginTop: 20 }}>
          {"There " + verb + openCount + " open " + noun}
        </div>
      </div>
    </>
  );
};

const BuggyButton = (openCount, setOpenCount) => {
  const [isOpen, setIsOpen] = useState(false);
  return isOpen ? (
    <img
      src={BUNNY}
      height={300}
      width={200}
      onClick={() => {
        setOpenCount(openCount - 1);
        setIsOpen(false);
      }}
    />
  ) : (
    <button
      onClick={() => {
        setOpenCount(openCount + 1);
        setIsOpen(true);
      }}
    >
      OPEN BUNNY
    </button>
  );
};

export default App;

Solution

The two issues at play are corrected in the code below. The first bug was in the arrow function passed to Javascript’s native Array.prototype.map() function in the BuggyButtons component. With arrow functions, if the code segment after => is wrapped in parentheses or is completely unwrapped, the value of the segment is implicitly returned by the function. If the line of code is instead wrapped in curly braces, it is a distinct code block and requires an explicit return statement. We can fix the buggy code by wrapping the div element in parentheses instead of curly braces, or instead by adding the return keyword. The second issue is in the signature of the BuggyButton component. Attributes passed to custom components with JSX are implicitly wrapped altogether in a props object, so the attributes must be either accessed directly from the object with dot notation or, as I have done below, destructured from the input.

import React, { useState } from "react";

const BUNNY =
  "https://images.pexels.com/photos/326012/pexels-photo-326012.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2";

function App() {
  return <BuggyButtons />;
}

const BuggyButtons = () => {
  const [openCount, setOpenCount] = useState(0);
  const IDs = [1, 2, 3, 4, 5];
  const verb = openCount == 1 ? "is " : "are ";
  const noun = openCount == 1 ? "bunny." : "bunnies.";

  return (
    <>
      <div
        style={{
          display: "flex",
          flexDirection: "column",
        }}
      >
        <div
          style={{ display: "flex", justifyContent: "center", marginTop: 50 }}
        >
          {IDs.map((item) => (
            <div
              style={{
                marginRight: 10,
              }}
              id={item}
            >
              <BuggyButton openCount={openCount} setOpenCount={setOpenCount} />
            </div>
          ))}
        </div>
        <div style={{ alignSelf: "center", marginTop: 20 }}>
          {"There " + verb + openCount + " open " + noun}
        </div>
      </div>
    </>
  );
};

const BuggyButton = ({ openCount, setOpenCount }) => {
  const [isOpen, setIsOpen] = useState(false);
  return isOpen ? (
    <img
      src={BUNNY}
      height={300}
      width={200}
      onClick={() => {
        setOpenCount(openCount - 1);
        setIsOpen(false);
      }}
    />
  ) : (
    <button
      onClick={() => {
        setOpenCount(openCount + 1);
        setIsOpen(true);
      }}
    >
      OPEN BUNNY
    </button>
  );
};

export default App;

useState