[React][API] Talk to a FastAPI Backend from React
Avoid hardcoded API URLs and infinite fetch loops - create a shared Axios instance for FastAPI and load data once with useEffect and an empty dependency array.

1. Environment
- OS: Windows 11 / macOS
- Node.js: v18 LTS or newer
- Frontend toolchain: Vite, React.js, Axios
- Backend: A running FastAPI server (e.g.
http://localhost:8000) - IDE: Visual Studio Code (VS Code)
2. Problem
Once the backend API is ready, it is time to build the UI.
Two common mistakes when calling APIs from React for the first time:
- Hardcoding the server URL inside every component
- Fetching without controlling the render lifecycle, which causes an infinite loop and can freeze the browser
3. Cause
Hardcoded URLs
If the backend moves to production or the port changes, you must hunt through hundreds of components to update addresses - expensive and error-prone.
React render lifecycle
React re-renders when state changes. If async loading runs as an uncontrolled side effect, you get: load data |–set state |–re-render |–load data again |–forever.
4. Solution
Scaffold a Vite React app, modularize Axios with a shared instance, and load data safely with useEffect.
1) Create the React project
Follow the earlier Vite + React + TypeScript setup: Create a React + Vite + TypeScript project
2) Install Axios
Axios is usually clearer than raw fetch and is easier to extend with interceptors in real projects:
npm install axios
# or: pnpm add axios / yarn add axios
3) Create a shared Axios instance
Add src/api.js (or api.ts) with the FastAPI base URL. All calls should go through this module:
// src/api.js
import axios from 'axios';
const api = axios.create({
// FastAPI backend
baseURL: 'http://localhost:8000',
headers: {
'Content-Type': 'application/json',
},
});
export default api;
4) Fetch once on mount in App.jsx
Replace App.jsx so useEffect loads data only when the component mounts (empty dependency array):
// src/App.jsx
import { useState, useEffect } from 'react';
import api from './api';
import './App.css';
function App() {
const [locations, setLocations] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchLocations = async () => {
try {
// baseURL is already set - only the path is needed
const response = await api.get('/api/city/locations');
setLocations(response.data.data); // array from FastAPI
} catch (error) {
console.error('Failed to load data:', error);
} finally {
setLoading(false);
}
};
fetchLocations();
}, []); // [] |--run once; prevents infinite re-fetch
return (
<div className="App">
<h1>Dashboard</h1>
{loading ? (
<p>Loading data|--/p>
) : (
<ul>
{locations.map((loc) => (
<li key={loc.id}>
<strong>{loc.name}</strong> |--lat: {loc.lat}, lng: {loc.lng}
</li>
))}
</ul>
)}
</div>
);
}
export default App;
5) Run with FastAPI up
Confirm the FastAPI server is running, then start the frontend:
npm run dev
Open the URL printed in the terminal. Backend data should appear on the page.
Related: async/await for API calls · async/await in API and UI layers
5. Summary
You now have a fullstack path: FastAPI on the backend and React on the frontend. Centralize the base URL in an Axios module for easier maintenance, and use useEffect with [] so the fetch lifecycle stays safe.


