How Do I Use Axios in React?

When I first started working with APIs in React, I ran into a mix of frustration and confusion. Fetching data felt like a simple enough task — until things got real with loading states, error handling, and canceling requests. That’s when I began using Axios. It wasn’t just about writing fewer lines of code; it made things more manageable and consistent. But even then, figuring out where to place the Axios logic, how to use it with useEffect, and what to do with errors took some experimenting. In this post, I want to share the way I’ve integrated Axios into React projects — from simple requests to reusable configurations — and the thinking behind each step.


Installing Axios

I usually start by adding Axios to the project via npm. It’s a simple command, and once it’s installed, I import it wherever I need it. Most of the time, I place the import at the top of the component file. But for larger apps, I eventually move configuration to a separate Axios instance. This way, I don’t repeat the base URL and headers every time.


Fetching Data in useEffect

Whenever I need to load data on component mount, I rely on useEffect. Inside it, I define an async function — usually called fetchData — and then invoke it. One of the biggest mistakes I used to make was declaring useEffect as async directly. It didn’t crash anything immediately, but it didn’t behave as expected either. So now, I keep the outer useEffect clean and nest the async call inside.

Axios makes this part smooth. The .get() method returns a promise, and once resolved, I update the local state. I’ve found this pattern more reliable than fetch, especially when I need to work with JSON by default or send additional headers.

In projects that involve complex effects and dependencies, combining Axios with useEffect brings back lessons I picked up while dealing with missing dependency issues, especially when the request parameters rely on props or changing state.


Managing Loading and Error States

When using Axios, I always track at least three pieces of state: the data, a loading flag, and an error object. This allows me to show skeleton loaders or spinners while the request is in progress and display messages when something goes wrong.

One thing I learned early on is to reset the error and loading state at the start of the request. That way, I don’t accidentally show stale errors when triggering the same request again. It’s a small touch but helps avoid awkward UI glitches.

Handling errors with Axios is more consistent too. The response object gives me access to both the HTTP status and the server’s custom message. In many cases, I use optional chaining to safely access nested fields without causing runtime crashes.


Creating a Reusable Axios Instance

In one of my larger projects, I kept copying the same base URL and authorization header across multiple files. It didn’t take long before I had duplicate code scattered everywhere. That’s when I created a dedicated Axios instance.

By calling axios.create() and defining default options there, I now have a single source of truth. I usually put this in a file like api.js or axiosClient.js and import it across the app. This lets me change headers or interceptors globally without rewriting every component.

In apps where I need consistent error formatting or retry logic, this pattern becomes incredibly helpful. It’s similar in concept to how centralized patterns improve reusability in tools like custom hooks or shared utilities — not just about DRY code, but better control overall.


Sending Data with POST, PUT, and DELETE

For actions like creating or updating data, I use .post() and .put() methods from Axios. I typically trigger these through form submissions. To avoid race conditions, especially with debounced input or double clicks, I often disable the submit button while the request is in progress.

In my experience, backend APIs differ in how they respond to POST/PUT actions — some return the new object, others just a status code. Axios doesn’t care either way; it lets me handle both consistently. I just check response.data or response.status, depending on the API’s behavior.

For deletes, I use .delete() with either an ID in the URL or in the request config. It’s always helpful to check whether the backend expects the ID as a path param or in the body — I’ve been tripped up by that a few times.


Handling Token-Based Auth

In apps with authentication, I usually store the token in localStorage or sessionStorage, and then inject it into the Axios instance headers. That way, every request includes the auth token automatically. I also use interceptors to catch 401 errors globally — this allows me to redirect users to the login screen when their session expires.

This has become a go-to pattern whenever I’m dealing with protected routes or backend APIs that require JWTs. It complements patterns I’ve used with protected routing in React where user redirection is tied directly to API response status.


Canceling Requests

In some edge cases — especially when dealing with fast-changing search inputs or switching between tabs — I cancel Axios requests using AbortController. This prevents memory leaks and ensures I’m not updating state on an unmounted component. It’s not something I do often, but when needed, it’s invaluable.

I’ve also seen this approach help improve perceived performance in apps with heavy data usage, like real-time dashboards or search-heavy tools. It keeps the component tree responsive and avoids unwanted flashes of outdated content.


Final Thoughts

Using Axios in React has helped me write cleaner and more maintainable code. From basic data fetching to handling authentication and global error messages, it brings structure to how requests are made and handled. Every time I work on a new feature that touches external APIs, I now think in terms of Axios flow: What needs to load? What triggers it? How do I cancel or retry it? These questions shape how I structure components — and they make debugging much easier down the line.

Whether it’s paired with useEffect, a custom hook, or a global config, Axios becomes more than just a utility — it becomes part of the architecture. And that’s what makes it worth learning well.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top