π A Modern React + Redux Toolkit + Axios Setup (Step-by-Step Guide)
Working with APIs in React becomes much easier when you combine Redux Toolkit + Axios with a clean, scalable folder structure.
This guide shows a production-style architecture that you can use in any real-world React project.
π Folder Structure
src/
β
βββ api/
β βββ api.js # axios instance (reusable)
β βββ endpoints.js # all API functions
β
βββ app/
β βββ store.js # Redux store setup
β
βββ features/
β βββ userSlice.js # Slice + async thunks
β
βββ App.jsx
βββ main.jsx
This structure keeps your API logic clean and helps scale your app easily.
*π οΈ 1. Reusable Axios Instance (api.js)
*
import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:5000/api",
});
// Attach token automatically
api.interceptors.request.use((config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = Bearer ${token};
}
return config;
});
// Global error handler
api.interceptors.response.use(
(res) => res,
(err) => {
console.error("API Error:", err.response?.data || err.message);
return Promise.reject(err);
}
);
export default api;
The benefits:
One place to manage API configuration
Auto token support
Clean, reusable API calls
**
π‘ 2. API Endpoints (endpoints.js)**
import api from "./api";
// GET /users
export const fetchAllUsers = () => api.get("/users");
// Examples:
// export const loginUser = (data) => api.post("/auth/login", data);
// export const createUser = (data) => api.post("/users", data);
This file keeps all API methods clean and organized.
βοΈ 3. Redux Slice + AsyncThunk (userSlice.js)
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { fetchAllUsers } from "../api/endpoints";
export const getUsers = createAsyncThunk("user/getUsers", async () => {
const res = await fetchAllUsers();
return res.data;
});
const userSlice = createSlice({
name: "user",
initialState: {
users: [],
loading: false,
error: null,
},
extraReducers: (builder) => {
builder
.addCase(getUsers.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(getUsers.fulfilled, (state, action) => {
state.loading = false;
state.users = action.payload;
})
.addCase(getUsers.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
},
});
export default userSlice.reducer;
This handles:
Loading state
Error state
Updating Redux store with API data
ποΈ 4. Configure the Store (store.js)
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "../features/userSlice";
export const store = configureStore({
reducer: {
user: userReducer,
},
});
π 5. Provider Setup (main.jsx)
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import { store } from "./app/store";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")).render(
);
π¨** 6. Using the API Data in React (App.jsx)**
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getUsers } from "./features/userSlice";
function App() {
const dispatch = useDispatch();
const { users, loading, error } = useSelector((state) => state.user);
useEffect(() => {
dispatch(getUsers());
}, [dispatch]);
if (loading) return
Loading...
;if (error) return
Error: {error}
;return (
Users List
{users.map((u) => (
{u.name}
))}
);
}
export default App;
π― Final Thoughts
With this setup you get:
π₯ Clean architecture
πReusable API code
β‘ Fast development with Redux Toolkit
π§© Scalable project structure
Top comments (0)