<!-- 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
This Pen doesn't use any external CSS resources.