Creating a React Component
Build a React component that fetches data from your REST API — display, create, and delete items through a modern React front-end.
Creating a React Component
In the previous episode you built a plain HTML/JS front-end. Now let us upgrade to React — a popular JavaScript library for building user interfaces. You will create a React component that fetches ninjas from the API, displays them, and handles create and delete operations.
Setting Up React
npx create-react-app ninja-client
cd ninja-client
npm start
The React dev server runs on port 3000 by default. Since your API also uses port 3000, either change the API port to 4000 or add a proxy.
Adding a Proxy
// In ninja-client/package.json, add:
"proxy": "http://localhost:4000"
The proxy tells the React dev server to forward API requests to your Express server. So fetch('/api/ninjas') in React goes to http://localhost:4000/api/ninjas.
The Ninja List Component
// src/NinjaList.js
import { useState, useEffect } from 'react';
function NinjaList() {
const [ninjas, setNinjas] = useState([]);
const [name, setName] = useState('');
const [rank, setRank] = useState('');
// Fetch ninjas on component mount
useEffect(() => {
loadNinjas();
}, []);
async function loadNinjas() {
const res = await fetch('/api/ninjas');
const data = await res.json();
setNinjas(data);
}
async function handleSubmit(e) {
e.preventDefault();
await fetch('/api/ninjas', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, rank: Number(rank) }),
});
setName('');
setRank('');
loadNinjas();
}
async function handleDelete(id) {
await fetch(`/api/ninjas/${id}`, { method: 'DELETE' });
loadNinjas();
}
return (
<div className="ninja-app">
<h1>Ninja Directory</h1>
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Ninja name"
required
/>
<input
type="number"
value={rank}
onChange={(e) => setRank(e.target.value)}
placeholder="Rank"
min="1"
max="10"
required
/>
<button type="submit">Add Ninja</button>
</form>
<div className="ninja-list">
{ninjas.map(ninja => (
<div key={ninja._id} className="ninja-card">
<h3>{ninja.name}</h3>
<p>Rank: {ninja.rank}</p>
<button onClick={() => handleDelete(ninja._id)}>
Delete
</button>
</div>
))}
</div>
</div>
);
}
export default NinjaList;
React Concepts Used
| Concept | Purpose |
|---|---|
useState | Manages component state (ninjas list, form inputs) |
useEffect | Runs side effects — fetches data when the component mounts |
fetch() | Sends HTTP requests to the REST API |
.map() | Renders a list of components from an array |
key | Unique identifier for each list item (React uses this for efficient re-rendering) |
Using the Component
// src/App.js
import NinjaList from './NinjaList';
function App() {
return (
<div className="App">
<NinjaList />
</div>
);
}
export default App;
Loading and Error States
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
async function loadNinjas() {
try {
setLoading(true);
const res = await fetch('/api/ninjas');
if (!res.ok) throw new Error('Failed to fetch');
const data = await res.json();
setNinjas(data);
setError(null);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
// In the JSX:
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
The Complete Architecture
React App (port 3000) Express API (port 4000) MongoDB
────────────────── ────────────────────── ────────
NinjaList component /api/ninjas ninjas
│ │ collection
│── fetch GET /api/ninjas ────→ │── Ninja.find() ──────→ │
│←── JSON array ────────────── │←── documents ──────────│
│ │ │
│── fetch POST /api/ninjas ───→ │── ninja.save() ──────→ │
│←── JSON new ninja ───────── │←── saved doc ──────────│
Key Takeaways
useEffectwith an empty dependency array ([]) fetches data once when the component mountsuseStatemanages the ninja list and form input values- The
proxyfield inpackage.jsonforwards API requests to your Express server - Always handle loading and error states for a good user experience
- After mutating data (POST, DELETE), re-fetch the list to keep the UI in sync
- The React front-end communicates with the Express API using the Fetch API — the same pattern works with any REST API