<!-- DO NOT REMOVE or change the ID of following div. It is used as placeholder for the app -->
<div id="app"></div>
html,
body {
  padding: 0;
  margin: 0px;
  display: flex;
  justify-content: center;
  font-family: Roboto, sans-serif;
  flex-direction: column;
}
@media (max-width: 320px) {
  h1 {
    font-size: 14px;
  }
}
#root {
  max-width: 400px;
}
h1 {
  border-style: solid;
  border-width: 0 2px 2px 2px;
  margin-top: 0;
  box-shadow: 0px 3px 4px gray;
  padding: 0 10px;
}
button {
  margin: 5px;
  padding: 5px 10px;
  font-size: 15px;
  border-radius: 5px;
  border: none;
  border-top: 2px solid green;
  background-color: lightgreen;
  height: 40px;
  min-width: 100px;
}

button.bad {
  border-top: 2px solid #880617;
  background: #f88493;
  color: #880617;
}

button.dangerous {
  border-top: 2px solid darkred;
  background-color: red;
  color: white;
}
.robotContainer {
  display: flex;
  justify-content: center;
  align-items: center;
}

.robotContainer img {
  border: 2px solid;
  border-radius: 5px;
}

.controls {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
}

.robotSelectorContainer {
  text-align: center;
  margin-bottom: 10px;
}

select {
  font: inherit;
  padding: 0.5rem 1rem;
  border-radius: 5px;
  height: 40px;
}

select.good {
  border: 2px solid green;
  background: lightgreen;
  color: green;
}

select.bad {
  border: 2px solid #880617;
  background: #f88493;
  color: #880617;
}

select:focus {
  outline: none;
}
/************************************/
//            Robot COMPONENT       
/************************************/
class Robot extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      loadedRobot: {},
      isLoading: false
    };
  }
  shouldComponentUpdate(nextProps, nextState) {
    console.log("Robot: shouldComponentUpdate");
    return (
      nextProps.selectedRobotId !== this.props.selectedRobotId ||
      nextState.loadedRobot.id !== this.state.loadedRobot.id ||
      nextState.isLoading !== this.state.isLoading
    );
  }

  componentDidUpdate(prevProps) {
    console.log("Robot: componentDidUpdate");
    if (prevProps.selectedRobotId !== this.props.selectedRobotId) {
      this.fetchData();
    }
  }

  componentDidMount() {
    console.log("Robot: componentDidMount");
    this.fetchData();
  }

  fetchData = () => {
    console.log(
      "http request to get new robot with id " + this.props.selectedRobotId
    );
    this.setState({ isLoading: true });
    fetch(`https://robohash.org/${this.props.selectedRobotId}`, {
      "Content-type": "application/octet-stream"
    })
      .then(response => {
        console.log(response);
        if (response.status !== 200) {
          throw new Error("Could not fetch the Robot");
        }
        return response.blob();
      })
      .then(robot => {
        console.log(window.URL.createObjectURL(robot));
        this.setState({
          isLoading: false,
          loadedRobot: window.URL.createObjectURL(robot)
        });
      })
      .catch(err => console.error(err));
  };
  componentWillUnmount() {
    console.log("Robot: componentWillUnmount");
  }
  render() {
        console.log("Robot: render");
    let content = <p>Loading robot...</p>;
    if (!this.state.isLoading && this.state.loadedRobot) {
      content = (
        <img
          width="150"
          src={this.state.loadedRobot}
          alt=""
          onLoad={function(event) {
            window.URL.revokeObjectURL(event.target.src);
          }}
        />
      );
    } else if (!this.state.isLoading && !this.state.loadedRobot) {
      content = <p>Failed to load robot!!!</p>;
    }

    return <div className="robotContainer">{content}</div>;
  }
}
/************************************/
//     RobotSelector COMPONENT     
/************************************/
class RobotSelector extends React.Component {
  state = { robots: [], isLoading: false };
  componentDidMount() {
    console.log('RobotSelector: componentDidMount');
    this.setState({ isLoading: true });
    fetch("https://swapi.co/api/species")
      .then(response => {
        if (!response.ok) {
          throw new Error("Failed to fetch.");
        }
        return response.json();
      })
      .then(robotData => {
        const selectedRobots = robotData.results.slice(0, 5);
        this.setState({
          robots: selectedRobots.map((robot, index) => ({
            name: robot.name,
            id: index + 1
          })),
          isLoading: false
        });
      })
      .catch(err => {
        console.log(err);
      });
  }

  render() {
    console.log('RobotSelector: render');
    let content = <p>Loading Robots...</p>;
    if (
      !this.state.isLoading &&
      this.state.robots &&
      this.state.robots.length > 0
    ) {
      content = (
        <select
          onChange={this.props.onRobotChange}
          value={this.props.selectedRobotsId}
          className={this.props.robotType}
        >
          {this.state.robots.map(robot => (
            <option key={robot.id} value={robot.id}>
              {robot.name}
            </option>
          ))}
        </select>
      );
    } else if (
      !this.state.isLoading &&
      (!this.state.robots || this.state.robots.length === 0)
    ) {
      content = <p>Could not fetch any data.</p>;
    }

    return <div className="robotSelectorContainer">{content}</div>;
  }
}

/************************************/
//            App COMPONENT        
/************************************/
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      selectedRobotId: 1,
      robotType: "good",
      destroyed: false
    };
  }

  onRobotChange = event => {
    const robotId = event.target.value;
    this.setState({ selectedRobotId: robotId });
  };
  onRobotTypeChange = robotType => {
    this.setState({ robotType });
  };
  onDestruction = () => {
    this.setState({ destroyed: true });
  };
  render() {
    if (this.state.destroyed) {
      return <h1>This bad robot destroyed everything!</h1>;
    }
    return (
      <React.Fragment>
        <h1>React Lifecycle with hooks</h1>
        <RobotSelector
          robotType={this.state.robotType}
          selectedRobotId={this.state.selectedRobotId}
          onRobotChange={this.onRobotChange}
        />
        <Robot selectedRobotId={this.state.selectedRobotId} />
        <div className="controls">
          <button onClick={this.onRobotTypeChange.bind(this, "good")}>
            Good Robot
          </button>
          <button
            className="bad"
            onClick={this.onRobotTypeChange.bind(this, "bad")}
          >
            Bad Robot
          </button>
          {this.state.robotType === "bad" && (
            <button className="dangerous" onClick={this.onDestruction}>
              Destroy
            </button>
          )}
        </div>
      </React.Fragment>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("app"));
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js