Topic 12: Networking
📖 11 min read · 🎯 advanced · 🧭 Prerequisites: introduction-to-redux, actions-and-reducers
Why this matters
Up until now, every React app you've built has lived entirely in the browser — the data was hardcoded, the UI was self-contained, and nothing talked to the outside world. That's fine for learning, but no real app works that way. Real apps fetch user profiles, load product lists, submit forms to a server. The moment you build something that actually does something, you need networking. In this lesson, we'll look at how to pull data from remote APIs using the Fetch API and Axios, how to POST data back to a server, and how to handle the async nature of it all cleanly with async/await.
What You'll Learn
- Make GET requests with the native Fetch API and render the results in a React component
- Install and use Axios as a promise-based alternative to Fetch for cleaner HTTP calls
- Submit data to a server via POST using both Fetch and Axios
- Write clean async/await data-fetching logic inside
useEffect - Build a practical posts-list feature that fetches, stores, and displays a collection from a live API
The Analogy
Think of your React component as a restaurant diner and the API server as the kitchen. When you call fetch() or axios.get(), you're placing an order — you don't stand frozen at the table until the food arrives. You keep chatting, reading the menu, sipping water. The moment the kitchen calls your number (Promise resolves), you update your tray (setState) and dig in. Async/await is just a polite headwaiter who handles the back-and-forth so you never have to shout .then().then().catch() across the room yourself.
Chapter 1: Introduction to Networking in React
Networking in web development means making HTTP requests to interact with servers. In React, networking is how components stay alive with real data: you fetch from an API on mount, the component re-renders with the response, and the user sees dynamic content instead of hard-coded placeholders.
The two tools the class reaches for most often are:
- Fetch API — built into modern browsers, no installation required, returns Promises
- Axios — a popular, promise-based HTTP client for both browser and Node.js that adds conveniences like automatic JSON parsing and request/response interceptors
Both tools live naturally inside useEffect, because a data fetch is a side effect — something that happens outside the pure render cycle.
Chapter 2: Using Fetch API
The Fetch API is built into every modern browser. It returns a Promise that resolves to a Response object, so you always need a .json() call to extract the body.
Fetching Data with Fetch API
src/components/FetchData.js:
import React, { useState, useEffect } from 'react';
function FetchData() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then((data) => {
setData(data);
setLoading(false);
})
.catch((error) => {
setError(error);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h2>{data.title}</h2>
<p>{data.body}</p>
</div>
);
}
export default FetchData;
Notice the explicit response.ok guard — Fetch does not throw on HTTP error status codes (4xx, 5xx). If the server returns a 404, the Promise still resolves; you must check response.ok yourself and throw manually.
Integrating FetchData Component
src/App.js:
import React from 'react';
import './App.css';
import FetchData from './components/FetchData';
function App() {
return (
<div className="App">
<h1>React Networking</h1>
<FetchData />
</div>
);
}
export default App;
Chapter 3: Using Axios
Axios is a promise-based HTTP client that works in both the browser and Node.js. Compared to Fetch, it automatically parses JSON responses (no .json() call needed), throws on non-2xx status codes by default, and supports request/response interceptors out of the box.
Installing Axios
npm install axios
Fetching Data with Axios
src/components/AxiosData.js:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function AxiosData() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => {
setData(response.data);
setLoading(false);
})
.catch((error) => {
setError(error);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h2>{data.title}</h2>
<p>{data.body}</p>
</div>
);
}
export default AxiosData;
The parsed body lives at response.data — Axios wraps the response in its own envelope. Compare this to Fetch's two-step response → response.json() chain.
Integrating AxiosData Component
src/App.js:
import React from 'react';
import './App.css';
import AxiosData from './components/AxiosData';
function App() {
return (
<div className="App">
<h1>React Networking with Axios</h1>
<AxiosData />
</div>
);
}
export default App;
Chapter 4: Submitting Data to the Server
Networking isn't read-only. The class next tackled POST requests — sending data to the server and handling the response.
Submitting Data with Fetch API
src/components/SubmitDataFetch.js:
import React, { useState } from 'react';
function SubmitDataFetch() {
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const [response, setResponse] = useState(null);
const handleSubmit = (event) => {
event.preventDefault();
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ title, body })
})
.then((response) => response.json())
.then((data) => setResponse(data))
.catch((error) => console.error('Error:', error));
};
return (
<div>
<form onSubmit={handleSubmit}>
<div>
<label>Title:</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
</div>
<div>
<label>Body:</label>
<textarea
value={body}
onChange={(e) => setBody(e.target.value)}
></textarea>
</div>
<button type="submit">Submit</button>
</form>
{response && (
<div>
<h2>Response:</h2>
<pre>{JSON.stringify(response, null, 2)}</pre>
</div>
)}
</div>
);
}
export default SubmitDataFetch;
The Content-Type: application/json header tells the server how to interpret the body. JSON.stringify({ title, body }) serializes the state values into a JSON string for the wire.
Submitting Data with Axios
src/components/SubmitDataAxios.js:
import React, { useState } from 'react';
import axios from 'axios';
function SubmitDataAxios() {
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const [response, setResponse] = useState(null);
const handleSubmit = async (event) => {
event.preventDefault();
try {
const res = await axios.post('https://jsonplaceholder.typicode.com/posts', {
title,
body
});
setResponse(res.data);
} catch (error) {
console.error('Error:', error);
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<div>
<label>Title:</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
</div>
<div>
<label>Body:</label>
<textarea
value={body}
onChange={(e) => setBody(e.target.value)}
></textarea>
</div>
<button type="submit">Submit</button>
</form>
{response && (
<div>
<h2>Response:</h2>
<pre>{JSON.stringify(response, null, 2)}</pre>
</div>
)}
</div>
);
}
export default SubmitDataAxios;
Notice how Axios accepts a plain JavaScript object as the second argument to axios.post() — it handles JSON.stringify and sets Content-Type: application/json for you automatically.
Chapter 5: Handling Asynchronous Operations with Async/Await
.then() chains work, but they get unwieldy fast. The async/await syntax lets you write asynchronous code that reads like synchronous code — far easier to reason about when requests depend on each other or when error handling spans multiple steps.
The one rule inside useEffect: you cannot make the effect callback itself async. Instead, define an inner async function and call it immediately.
src/components/AsyncAwaitData.js:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function AsyncAwaitData() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts/1');
setData(response.data);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h2>{data.title}</h2>
<p>{data.body}</p>
</div>
);
}
export default AsyncAwaitData;
The try/catch block cleanly replaces .catch() — any await that throws lands in the catch, keeping error handling centralized.
sequenceDiagram
participant Component
participant useEffect
participant Server
Component->>useEffect: mounts, effect fires
useEffect->>Server: axios.get(url)
Note over useEffect: awaiting...
Server-->>useEffect: 200 OK { data }
useEffect->>Component: setData(response.data), setLoading(false)
Component->>Component: re-render with real data
Chapter 6: Practical Example — Fetching and Displaying a List
To cement everything, the class built a real feature: fetch the full list of posts from the JSONPlaceholder API and render them as a list.
Fetching and Displaying Posts
src/components/PostsList.js:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function PostsList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchPosts = async () => {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
setPosts(response.data);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
};
fetchPosts();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>Posts</h1>
<ul>
{posts.map(post => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
))}
</ul>
</div>
);
}
export default PostsList;
posts is initialized as an empty array [] — not null — so the .map() call is safe even before the data arrives.
Integrating PostsList Component
src/App.js:
import React from 'react';
import './App.css';
import PostsList from './components/PostsList';
function App() {
return (
<div className="App">
<h1>React Networking Example</h1>
<PostsList />
</div>
);
}
export default App;
🧪 Try It Yourself
Task: Build a UserCard component that fetches a single user from https://jsonplaceholder.typicode.com/users/1 using Axios with async/await, then displays their name, email, and company.name.
Success criterion: You should see the user's name, email address, and company name rendered in the browser. While the fetch is in-flight, a "Loading…" message appears. If the request fails (try an invalid URL), an error message appears instead.
Starter snippet:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function UserCard() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/users/1');
setUser(response.data);
setLoading(false);
} catch (err) {
setError(err);
setLoading(false);
}
};
fetchUser();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{/* Render user.name, user.email, user.company.name here */}
</div>
);
}
export default UserCard;
🔍 Checkpoint Quiz
Q1. Why can't you make the useEffect callback itself an async function?
A) React doesn't support ES2017 syntax
B) useEffect expects its callback to return undefined or a cleanup function, but async functions always return a Promise
C) Async functions can't call setState
D) You can — it's just a style preference to avoid it
Q2. Given this Fetch-based code, what happens when the server returns a 404?
fetch('/api/missing')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error('caught:', err));
A) The .catch() handler fires because 404 is an error
B) The first .then() fires, res.json() is called, and the data (likely an error body) is logged — no catch
C) The request is retried automatically
D) The browser blocks the fetch and throws a CORS error
Q3. What is the key difference between fetch() and axios.get() when handling the response body?
A) Axios requires response.text() while Fetch uses response.json()
B) Fetch auto-parses JSON; Axios requires a manual .json() call
C) Axios automatically parses JSON into response.data; Fetch requires an explicit .json() call that returns another Promise
D) There is no difference — they both return the parsed body directly
Q4. You want to POST the object { title: "Hello", body: "World" } to an API using fetch. Which call is correct?
A) fetch(url, { method: 'POST', body: { title: "Hello", body: "World" } })
B) fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: "Hello", body: "World" }) })
C) fetch(url).post({ title: "Hello", body: "World" })
D) fetch(url, { type: 'POST', data: JSON.stringify({ title: "Hello", body: "World" }) })
A1. B — useEffect treats a returned Promise as an unknown value and ignores it, but more importantly the cleanup contract breaks. The correct pattern is to define an inner async function and call it immediately inside the synchronous effect callback.
A2. B — Fetch only rejects its Promise on network failure (no connection, DNS error, etc.). A 404 is a valid HTTP response, so the Promise resolves. The res.ok flag is false, but without checking it you'll proceed into .then() and try to parse whatever error body the server sent.
A3. C — Axios wraps the response and exposes the already-parsed JSON at response.data. Fetch's Response object requires you to call response.json(), which itself returns a Promise you must chain.
A4. B — Fetch needs the method, the Content-Type header (so the server knows it's JSON), and the body serialized with JSON.stringify. Passing a plain object to body would send [object Object] as a string.
🪞 Recap
- The Fetch API is browser-native; it resolves on any HTTP response, so always check
response.okbefore calling.json(). - Axios auto-parses JSON into
response.dataand throws on non-2xx status codes, reducing boilerplate for error handling. - Install Axios with
npm install axios; import it asimport axios from 'axios'. - Never mark the
useEffectcallbackasync— define an inner async function and call it inside the synchronous callback. - For POST requests, Fetch requires
JSON.stringifyand aContent-Typeheader; Axios accepts a plain object and sets the header automatically.
📚 Further Reading
- MDN — Using the Fetch API — the source of truth on Fetch,
Response.ok, and streaming - Axios documentation — full reference for interceptors, instances, and config defaults
- JSONPlaceholder — the free fake REST API used in every example above, great for practice
- ⬅️ Previous: Actions and Reducers
- ➡️ Next: React Forms