Simplifying API Instance Setup with Token Refresh in React/React Native

Suki Nhung Phan
4 min readDec 8, 2023

Hi, I’m Suki, front-end developer. In this blog, I’ll guide you on simplifying the setup of an API instance in React/React Native using Axios, AsyncStorage(for React Native) or LocalStorage(in React), and token refresh logic. Managing authentication in React Native, especially handling token expiration and refresh, can be complex. Follow along for a seamless integration of token management in your app.

Prerequisites

Before we begin, ensure you have the necessary packages installed:

npm install @react-native-async-storage/async-storage axios

Setting Up the API Instance

First, create an instance of Axios to manage your API requests. In simpler terms, Axios is a tool to make HTTP requests, and we’re setting it up to talk to our server. Add this code to a file, e.g., api.js:

// api.js

// Import necessary packages
import AsyncStorage from "@react-native-async-storage/async-storage";
import axios from "axios";

// Define your API and identity URLs
const API_URL = "<YOUR_BASE_API_URL>";

export const ApiClient = () => {
// Create a new axios instance
const api = axios.create({
baseURL: API_URL,
headers: {
"Content-Type": "application/json",
},
});

// Add a request interceptor to add the JWT token to the authorization header
api.interceptors.request.use(
async (config) => {
const token =await AsyncStorage.getItem("token");
if (token) {
config.headers.Authorization = `${token}`
}
return config;
},
(error) => Promise.reject(error)
);

createAxiosResponseInterceptor();

function createAxiosResponseInterceptor() {
const interceptor = api.interceptors.response.use(
(response) => {
console.log("RETURN REPONSE INSTEAD ERRRO")
return response
},
async (error) => {
console.log(error.response.status)
// Reject promise if usual error
if (error.response.status !== 401) {
return Promise.reject(error);
}
if (
error.response.status === 401 &&
AsyncStorage.getItem("refreshToken")
) {
try {
api.interceptors.response.eject(interceptor);

const refreshToken = await AsyncStorage.getItem("refreshToken");
console.error("Error at API AXIOS", error.response.status, refreshToken)

const url = `${YOUR_REFRESH_TOKEN_URL}`

const body = `grant_type=refresh_token&client_id=${clientId}&scope=${scope}&refresh_token=${refreshToken}`;
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};

const response = await axios.post(url, body, { headers: headers })

await AsyncStorage.setItem("token", "Bearer " + response.data.access_token);
await AsyncStorage.setItem("refreshToken", response.data.refresh_token);
console.LOG("Successfully refresh token")

error.response.config.headers["Authorization"] =
"Bearer " + response.data.access_token;
return axios(error.response.config)
}
catch(err) {
console.error("Error at refresh token", err.response.status)

//If refresh token is invalid, you will receive this error status and log user out
if (err.response.status === 400) {
throw { response: { status: 401 } };
}
return Promise.reject(err);
}
finally{createAxiosResponseInterceptor};
}
}
);
}

const get = (path, params) => {
return api.get(path,{params}).then((response) => response);
};

const post = (path, body, params) => {
return api.post(path, body, params).then((response) => response);
};

const put = (path, body, params) => {
return api.put(path, body, params).then((response) => response);
};

const patch = (path, body, params) => {
return api.patch(path, body, params).then((response) => response);
};

const del = (path) => {
return api.delete(path).then((response) => response);
};



return {
get,
post,
patch,
put,
del,
};
};

How it works?

  1. Imports: It imports necessary packages such as AsyncStorage for storing tokens locally, axios for making HTTP requests.
  2. API URL: It defines the base URL for the API. This should be replaced with the actual base URL of your API.
  3. ApiClient Function: It defines a function ApiClient which creates and configures an Axios instance for making HTTP requests.
  4. Request Interceptor: It adds a request interceptor to the Axios instance. This interceptor is executed before each request is sent. It retrieves the token from AsyncStorage and adds it to the Authorization header of the request.
  5. Response Interceptor: It adds a response interceptor to the Axios instance. Before the response is sent to client application, this interceptor is executed. If the response status is not 401 (Unauthorized), it simply returns the response. If the response status is 401 and a refresh token is available, it tries to refresh the access token using the refresh token. If the refresh token is invalid, it logs out the user. If an error occurs during the refresh token process, it rejects the promise with the error.
  6. Request Methods: It defines methods (get, post, put, patch, delete) for making HTTP requests. These methods use the Axios instance to make requests and return a promise that resolves with the response data.

Making an API call

import { ApiClient } from "./api"; // Importing the instance from api.js

const apiPath = ApiClient()

// Make an API call using the instance with the interceptor
apiPath.get('/example-endpoint', null)
.then(response => {
// Handle the successful response
console.log('API Response:', response.data);
})
.catch(error => {
// Handle errors, including token-related errors
console.error('API Error:', error);
});

Conclusion

And just like that. Your React Native is now ready to corporate seamlessly with the back-end. Thanks for reading and happy coding! ❤️😊

--

--