Topic 12: Networking
📖 11 min read · 🎯 advanced · 🧭 Prerequisites: redux, firebase
Why this matters
Up until now, your React components have been living in their own little world — hardcoded data, everything static, nothing real. But real apps don't work that way. A real app talks to a server. It asks for data, sends data back, and updates what you see on screen based on what comes back. That's networking. When you open a food delivery app and see live menu prices, or submit a login form and get redirected — that's React talking to an API over HTTP. This lesson is where your frontend stops being a mockup and starts being a real, connected application.
What You'll Learn
- Make GET requests using the browser-native Fetch API and handle loading, error, and success states
- Install and use Axios as a promise-based HTTP client for both GET and POST requests
- Submit form data to a server with both Fetch and Axios
- Write cleaner async network code using
async/awaitinsideuseEffect - Build a practical posts-list feature that fetches and renders a collection from a REST API
The Analogy
Think of your React component as a restaurant diner and the server (pun intended) as the kitchen. When you make an HTTP request, you're handing a written order to the waiter — the Fetch API or Axios. The kitchen processes it and sends a tray back. While you wait, you don't freeze at the table; you read the menu, check your phone, stay interactive. That's the asynchronous nature of networking: the component keeps rendering while the request is in-flight, then updates the moment the tray arrives. A loading spinner is you drumming your fingers; the error state is the waiter returning with "sorry, we're out of that."
Chapter 1: Introduction to Networking in React
Networking in web development means making HTTP requests to interact with servers. In React, these requests are almost always asynchronous — they happen after the component renders, triggered by useEffect or a user action.
The three core states you must always model:
- loading — the request is in-flight; show a spinner or skeleton
- error — something went wrong; show a helpful message
- data — the response arrived; render the content
React gives you no built-in networking primitive. Instead, you choose a tool: the browser-native Fetch API or the library Axios. Both return Promises; both integrate naturally with async/await.
Chapter 2: Using Fetch API
The Fetch API ships with every modern browser — no install required. It returns a Promise that resolves to a Response object. Critically, Fetch does not reject on HTTP error status codes (like 404 or 500); you must check response.ok manually.
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;
Key points:
- The empty dependency array
[]means the effect runs once, on mount. response.okistruefor status codes 200–299. Any other status requires manual error throwing.response.json()is itself asynchronous — it returns another Promise, so it must be chained with.then().
Integrating FetchData into the App
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 popular promise-based HTTP client for both the browser and Node.js. It improves on Fetch in several ways: it automatically parses JSON responses, throws errors for non-2xx status codes by default, and provides a cleaner API surface for setting headers, timeouts, and interceptors.
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;
Notice the difference from Fetch: response.data already contains the parsed JSON — no .json() call needed.
Integrating AxiosData into the App
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
GET requests read data. POST requests write it. Both Fetch and Axios handle POST, but their ergonomics differ.
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;
With Fetch, you must manually set 'Content-Type': 'application/json' and serialize the body with JSON.stringify().
Submitting Data with Axios
Axios handles JSON serialization automatically when you pass a plain object as the second argument to axios.post(). The async/await syntax also cleans up the control flow.
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;
Chapter 5: Handling Asynchronous Operations with Async/Await
Promise chaining with .then() works, but async/await reads like synchronous code — which makes it far easier to reason about, especially when multiple requests depend on each other.
The constraint inside useEffect: the callback passed to useEffect cannot itself be async. The workaround is to 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 pattern: define fetchData as async, wrap the await calls in try/catch, then invoke fetchData() at the bottom of the useEffect body.
sequenceDiagram
participant Component
participant useEffect
participant fetchData
participant API
Component->>useEffect: mount ([] dependency)
useEffect->>fetchData: call async function
fetchData->>API: axios.get(url)
API-->>fetchData: response.data
fetchData->>Component: setData(response.data), setLoading(false)
Note over Component: re-renders with data
Chapter 6: Practical Example — Fetching and Displaying a List
The class's final exercise: pull the full posts collection from the API and render each post as a list item.
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;
Note the key={post.id} on each <li> — React requires a stable, unique key when rendering lists from dynamic data.
Integrating PostsList into the App
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 async/await with Axios, and displays their name, email, and company.name.
Success criterion: You should see the user's name, email address, and company name rendered on screen — no console errors, and a "Loading..." message that appears briefly before the data arrives.
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 does a useEffect callback with an empty dependency array [] run only once, and when exactly does it run?
A) Before the first render, to pre-fetch data
B) Once, immediately after the component's first render
C) Every time any state in the component changes
D) Only when the user triggers an event
Q2. Given this Fetch-based snippet, what happens if the server returns a 404 status?
fetch('https://example.com/api/thing')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error('caught:', err));
A) The .catch() handler fires with a network error
B) The .then(res => res.json()) handler fires; the 404 is not automatically treated as an error
C) The browser throws a TypeError before any .then() runs
D) res.json() returns null for non-2xx responses
Q3. What is the key ergonomic difference between axios.post(url, { title, body }) and the equivalent fetch() call?
A) Axios cannot send POST requests in the browser
B) Axios automatically serializes the object to JSON and sets Content-Type: application/json; Fetch requires you to call JSON.stringify() and set the header manually
C) fetch() uses response.data while Axios uses response.json()
D) There is no meaningful difference between the two
Q4. You want to fetch two independent endpoints — /users/1 and /posts?userId=1 — at the same time, rather than sequentially. How would you do this with Axios and async/await?
A) Call await axios.get(url1) then await axios.get(url2) on separate lines
B) Use Promise.all([axios.get(url1), axios.get(url2)]) and await the result
C) You cannot make two requests in the same useEffect
D) Use two separate useEffect hooks, each with its own dependency array
A1. B — The effect runs once, after the component's first render (after the DOM has been painted). The empty array tells React there are no dependencies to watch, so it never re-runs.
A2. B — Fetch only rejects its Promise on a network failure (no connection, DNS error, etc.). A 404 is a valid HTTP response, so .then(res => res.json()) fires. You must check res.ok yourself and throw manually to route into .catch().
A3. B — Axios automatically serializes a plain JavaScript object to JSON and sets the Content-Type: application/json header. With Fetch, you must call JSON.stringify() on the body and add the header explicitly; forgetting either causes the server to misinterpret the payload.
A4. B — Promise.all fires both requests simultaneously and resolves when both complete, returning an array of responses. Awaiting them sequentially (option A) means the second request doesn't start until the first finishes — wasted time when the calls are independent.
🪞 Recap
- The Fetch API is browser-native and requires manual
response.okchecking andJSON.stringify()for POST bodies; Axios handles both automatically. - Always model three states for any network request:
loading,error, and the success data state. useEffectwith an empty[]dependency array is the standard place to trigger data fetching on component mount.- Define an inner
asyncfunction insideuseEffectand call it immediately — theuseEffectcallback itself cannot beasync. async/awaitpaired withtry/catchproduces cleaner, more readable async code than nested.then().catch()chains.
📚 Further Reading
- MDN — Using the Fetch API — the source of truth on Fetch, including streaming and advanced options
- Axios GitHub & Docs — full Axios API reference including interceptors, instances, and cancellation
- JSONPlaceholder — the free fake REST API used in every example above; great for practice
- ⬅️ Previous: Firebase
- ➡️ Next: Generating Signed APK