Handling API error responses with axios (multiple files) - reactjs

I'm new with React and I was trying to separate my Axios http utilities in different files.
It works fine if I login with correct credentials but when I try wrong credentials and my API responses with a 401 (Bad Credentials) message Axios execute the then() method instead of the catch() method.
axios.ts
import Axios from "axios";
const JSON_CONTENT_TYPE = "application/json";
// axios configuration
const axios = Axios.create({
baseURL: process.env.REACT_APP_API_URL,
responseType: "json"
});
// ... other requests handlers
export const post = <T = any>(
url: string,
body: any,
params?: any,
contentType = JSON_CONTENT_TYPE
) => {
return axios.post<T>(url, body, {
params,
headers: { "Content-Type": contentType }
});
};
login-adapter.ts
import { ILogin } from "../../model/login.model";
import * as Http from "../axios";
import * as StorageManager from "../storage-manager";
type TokenBody = {
id_token: string;
};
export const login = (credentials: ILogin) => {
return new Promise((resolve, reject) => {
Http.post<TokenBody>("/authenticate", credentials)
.then((resp) => {
// Stores jwt in local/session storage.
// HERE IS WHEN MY APP CRASHES, The error says 'resp is undefined' and THIS ERROR (not the response from my API) is caught by the catch method below.
StorageManager.setToken(resp.data.id_token, credentials.rememberMe);
// Does another request to get user info.
Http.get("/account").then(console.log);
resolve("Success");
})
.catch((error) => reject("Error, " + error.response.data)); // THIS SHOULD SEND THE ERROR MESSAGE TO LoginPage.tsx
});
};
LoginPage.tsx
import { FormEvent, useState } from "react";
import "../../styles/LoginPage.css";
import * as LoginAdapter from "../../adapters/loginAdapters/login-adapter";
import { RouteComponentProps } from "react-router-dom";
const LoginPage = ({history}: RouteComponentProps) = {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [rememberMe, setRememberMe] = useState(false);
const submit = (e: FormEvent<HTMLFormElement>) => {
LoginAdapter.login({ email, password, rememberMe })
.then(() => {
history.push("/admin/courses");
})
.catch((error) => {
console.log(error);
});
e.preventDefault();
}
return (
<form onSubmit={submit}>
{/* <input ... email, password, and 'remember me' form fields.*/}
</form>
);
}
export default LoginPage;
When I use axios directly from the package. (import axios from "axios") it works perfectly. But I have to rewrite my api endpoint, response type, interceptors, etc. I don't know why it is not working, Am I missing something?

My interceptor was the problem, I didn't notice that the error handler should return a Promise with a reject reason.
axios.interceptors.response.use(
(resp) => resp,
(error) => {
if (
error.response.status === 401 &&
error.response.config.url !== "/account"
) {
LoginAdapter.logout();
}
// before: <nothing>
// now:
return Promise.reject(error);
}
);

Related

Twitter user search to display name, followers, following, among others using React.js

I am fairly new to react.js and I'm just trying my hands on a few random projects i can think of and one of them is to make a search engine in react.js that looks up users on twitter by simply entering their name in a search bar and the result will display their details using the Twitter API. However, when doing this i am hit with the follwoing errors in console:
Error ocuring
App.js:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const App = ({ username }) => {
const [user, setUser] = useState({});
const [tweets, setTweets] = useState({});
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const { data: user } = await axios.get(`https://api.twitter.com/1.1/users/show.json?screen_name=${username}`, {
method : "GET",
headers: {
Authorization: `Bearer <YOUR_TOKEN>`
}
});
const { data: tweets } = await axios.get(`https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=${username}&count=200`, {
method : "GET",
headers: {
Authorization: `Bearer <YOUR_TOKEN>`
}
});
setUser(user);
setTweets(tweets);
} catch (error) {
setError(error);
}
};
fetchData();
}, [username]);
if (error) {
return <div>An error occurred: {error.message}</div>;
}
return (
<div>
<h1>{user.name}</h1>
<p>Username: {user.screen_name}</p>
<p>Followers: {user.followers_count}</p>
<p>Following: {user.friends_count}</p>
<p>Bio: {user.description}</p>
<p>Date Joined: {user.created_at}</p>
<p>Pinned Tweet: {user.status ? user.status.text : 'No Pinned Tweet'}</p>
<p>Total Tweets: {user.statuses_count}</p>
</div>
);
};
export default App;
UPDATE
I have added the search box feature to the code but I'm still getting the same errors
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const TWITTER_API_URL = 'https://api.twitter.com/1.1/users/search.json';
function App() {
const [username, setUsername] = useState('');
const [userData, setUserData] = useState({});
const [searchValue, setSearchValue] = useState('');
useEffect(() => {
if (searchValue) {
axios
.get(TWITTER_API_URL, {
params: {
q: searchValue,
count: 1
},
headers: {
'Authorization': 'Bearer YOUR_BEARER_TOKEN'
}
})
.then(response => {
setUsername(response.data[0].screen_name);
})
.catch(error => {
console.log(error);
});
}
}, [searchValue]);
useEffect(() => {
if (username) {
axios
.get(`https://api.twitter.com/1.1/users/show.json?screen_name=${username}`, {
headers: {
'Authorization': 'Bearer YOUR_BEARER_TOKEN'
}
})
.then(response => {
setUserData(response.data);
})
.catch(error => {
console.log(error);
});
}
}, [username]);
return (
<div>
<input
type="text"
placeholder="Search by name"
value={searchValue}
onChange={e => setSearchValue(e.target.value)}
/>
{username && (
<div>
<p>Username: {username}</p>
<p>Name: {userData.name}</p>
<p>Following: {userData.friends_count}</p>
<p>Followers: {userData.followers_count}</p>
<p>Bio: {userData.description}</p>
<p>Date Joined: {userData.created_at}</p>
<p>Pinned Tweet: {userData.status.text}</p>
<p>Total Tweets: {userData.statuses_count}</p>
</div>
)}
</div>
);
}
export default App;
I would appreiciate any help given to resolve this issue. Thank you.
I would advise you to move the const fetchData = async () => { ... outside the useEffect() and may sound silly, but for the Authorization: Bearer <YOUR_TOKEN> have you changed the <YOUR_TOKEN> with your actual token? Lastly, you don't need method : "GET" because you are doing axios.get( ...
Please try this code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const App = ({ username }) => {
const [user, setUser] = useState({});
const [tweets, setTweets] = useState({});
const [error, setError] = useState(null);
const fetchData = async () => {
try {
const { data: user } = await axios.get(`https://api.twitter.com/1.1/users/show.json?screen_name=${username}`, {
headers: {
Authorization: `Bearer <YOUR_TOKEN>`
}
});
const { data: tweets } = await axios.get(`https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=${username}&count=200`, {
headers: {
Authorization: `Bearer <YOUR_TOKEN>`
}
});
setUser(user);
setTweets(tweets);
} catch (error) {
setError(error);
}
};
useEffect(() => {
fetchData();
}, [username]);
if (error) {
return <div>An error occurred: {error.message}</div>;
}
return (
<div>
<h1>{user.name}</h1>
<p>Username: {user.screen_name}</p>
<p>Followers: {user.followers_count}</p>
<p>Following: {user.friends_count}</p>
<p>Bio: {user.description}</p>
<p>Date Joined: {user.created_at}</p>
<p>Pinned Tweet: {user.status ? user.status.text : 'No Pinned Tweet'}</p>
<p>Total Tweets: {user.statuses_count}</p>
</div>
);
};
export default App;
The error message you are seeing is related to CORS (Cross-Origin Resource Sharing) and it is preventing your JavaScript code running on "http://localhost:3000" from making a request to "https://api.twitter.com".
CORS is a security feature implemented by web browsers that prevents a web page from making requests to a different domain than the one that served the web page.
To fix this issue, you will need to set up CORS headers on the server side. The "Access-Control-Allow-Origin" header is used to specify which domains are allowed to make requests to the server. You can set this header to "*" to allow any domain to make requests, or you can set it to the specific domain that your application is running on, "http://localhost:3000" in your case.
You can also use a proxy server in order to avoid CORS issue when trying to access twitter's API. This means that your react application will send the request to your server which will then forward it to twitter's API. It will then receive the response, and forward it back to your react application. This way your application will never be blocked by the CORS policy, as the request is coming from your server and not directly from your application.

Google OAuth components must be used within GoogleOAuthProvider

I want to build my next js project in which i am using
https://www.npmjs.com/package/#react-oauth/google
but when I build it i get the following :
this is layout.js and in _app.js I have all the components wrapped in GoogleOAuthProvider
import { GoogleLogin } from '#react-oauth/google';
import {FcGoogle} from "react-icons/Fc"
import { useGoogleLogin } from '#react-oauth/google';
export default function Layout({ children }) {
const client_id = ""
const responseGoogle = (response) => {
console.log(response);
}
CUTTED (NOT RELEVANT)
const login = useGoogleLogin({
onSuccess: codeResponse => {
const { code } = codeResponse;
console.log(codeResponse)
axios.post("http://localhost:8080/api/create-tokens", { code }).then(response => {
const { res, tokens } = response.data;
const refresh_token = tokens["refresh_token"];
const db = getFirestore(app)
updateDoc(doc(db, 'links', handle), {
refresh_token : refresh_token
})
updateDoc(doc(db, 'users', useruid), {
refresh_token : refresh_token
}).then(
CUTTED (NOT RELEVANT)
)
}).catch(err => {
console.log(err.message);
})
},
onError: errorResponse => console.log(errorResponse),
flow: "auth-code",
scope: "https://www.googleapis.com/auth/calendar"
});
return (
<>
CUTTED (NOT RELEVANT)
</>
)
}
Everything works perfect in dev mode but it does not want to build
I've faced this issue too. So I use 'GoogleLogin' instead of 'useGoogleLogin', then you can custom POST method on 'onSuccess' property.
import { GoogleLogin, GoogleOAuthenProvider} from '#react-oauth/google';
return(
<GoogleOAuthProvider clientId="YOUR CLIENT ID">
<GoogleLogin
onSuccess={handleLogin}
/>
</GoogleOAuthProvider>
The async function will be like...
const handleLogin = async = (credentialResponse) => {
var obj = jwt_decode(credentialResponse.credential);
var data = JSON.stringify(obj);
console.log(data);
const data = {your data to send to server};
const config = {
method: 'POST',
url: 'your backend server or endpoint',
headers: {},
data: data
}
await axios(config)
}
Spending whole day, this solve me out. Just want to share.
You have to wrap your application within GoogleOAuthProvider component. Please keep in mind that you will need your client ID for this.
import { GoogleOAuthProvider } from '#react-oauth/google';
<GoogleOAuthProvider clientId="<your_client_id>">
<SomeComponent />
...
<GoogleLoginButton onClick={handleGoogleLogin}/>
</GoogleOAuthProvider>;

how to use an Axios interceptor with Next-Auth

I am converting my CRA app to Nextjs and running into some issues with my Axios interceptor pattern.
It works, but I am forced to create and pass an Axios instance to every api call.
Is there a better way to do this?
Here is what I have now:
Profile.js:
import { useSession } from 'next-auth/react'
function Profile(props) {
const { data: session } = useSession()
const [user, setUser] = useState()
useEffect(()=> {
const proc= async ()=> {
const user = await getUser(session?.user?.userId)
setUser(user)
}
proc()
},[])
return <div> Hello {user.userName}<div>
}
getUser.js:
export default async function getUser(userId) {
const axiosInstance = useAxios()
const url = apiBase + `/user/${userId}`
const { data } = await axiosInstance.get(url)
return data
}
useAxios.js:
import axios from 'axios'
import { useSession } from 'next-auth/react'
const getInstance = (token) => {
const axiosApiInstance = axios.create()
axiosApiInstance.interceptors.request.use(
(config) => {
if (token && !config.url.includes('authenticate')) {
config.headers.common = {
Authorization: `${token}`
}
}
return config
},
(error) => {
Promise.reject(error)
}
)
return axiosApiInstance
}
export default function useAxios() {
const session = useSession()
const token = session?.data?.token?.accessToken
return getInstance(token)
}
In case anyone else has this problem, this was how i solved it (using getSession):
credit to:
https://github.com/nextauthjs/next-auth/discussions/3550#discussioncomment-1993281
import axios from 'axios'
import { getSession } from 'next-auth/react'
const ApiClient = () => {
const instance = axios.create()
instance.interceptors.request.use(async (request) => {
const session = await getSession()
if (session) {
request.headers.common = {
Authorization: `${session.token.accessToken}`
}
}
return request
})
instance.interceptors.response.use(
(response) => {
return response
},
(error) => {
console.log(`error`, error)
}
)
return instance
}
export default ApiClient()
There is actually a neat way on including user extended details to session object
// /api/[...nextauth].ts
...
callbacks: {
session({ session, user, token }) {
// fetch user profile here. you could utilize contents of token and user
const profile = getUser(user.userId)
// once done above, you can now attach profile to session object
session.profile = profile;
return session;
}
},
The you could utilize it as:
const { data: session } = useSession()
// Should display profile details not included in session.user
console.log(session.profile)
I know one way to do this is to use
const session = await getSession()
Is there any other way to go about it without using await getSession() because what this does is that it makes a network request to get your session every time your Axios request runs?

How to send request using customized Hooks in React Hooks

I was trying to create a custom Hooks for handling input HTTP request from any component by simply calling the useHttpPOSTHandler and want to use .then with Axios but its getting failed and error is
as i am new in react not able to debug this
what i have tried
import { useEffect, useState } from "react";
import axios from "axios";
const useHttpPOSTHandler = ({url , data}) => {
const [httpData, setHttpData] = useState();
const apiMethod = useCallback( ({url , data}) => {
axios
.post(url , data)
.then((response) => {
console.log(response)
console.log(response.data)
setHttpData(response.data);
})
.catch((error) => {
console.log(error);
});
}, [setHttpData])
return [ httpData , apiMethod];
};
export default useHttpPOSTHandler;
The arguments to useHTTPPostHandler are expected to be an object with keys url and data instead you are passing them individually causing a syntax error, wrap them within {}
const getData = useHttpPOSTHandler(
{ url: 'url', data: { "password": userPassword, "username": userName }
});
EDIT: As per your update, you won't see the updated data as soon as you make an API call. It will reflect in the next render
import useHttpPOSTHandler from "...."
const MyFunc = () => {
const [httpData, apiMethod] = useHttpPOSTHandlerdotthen()
const handleSubmit = () => {
apiMethod({url: 'url' , data: { "password": userPassword, "username": userName }})
}
if(httpData){
console.log("entered in api method")
console.log(httpData)
}
return (
<div>
...
</div>
)
}

How to create a custom Hooks in reactjs hooks?

I was trying to create a custom Hooks for handling input HTTP request from any component by simply calling the useCustomHooks but its getting failed and error is
Can not use keyword 'await' outside an async function
All i made is a handler that triggers http request custom component method
import { useState } from 'react';
import axios from "axios";
const useHttpReqHandler = () => {
const [result, setResult] = useState();
const apiMethod = async ({url , data , method}) => {
let options = {
method,
url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
data
};
let response = await axios(options);
const UpdatedData = await response.data;
console.log(UpdatedData)
setResult(UpdatedData);
}
return [result, apiMethod];
};
export default useHttpReqHandler;
Now i can use this hook in my code and on any event handler just call callAPI returned from the hook like this
const MyFunc = () => {
const [apiResult, apiMethod] = useHttpReqHandler();
const captchValidation = () => {
const x = result.toString();;
const y = inputValue.toString();;
if ( x === y) {
apiMethod({url: 'some url here', data: {"email": email}, method: 'post'});
alert("success")
}
else {
alert("fail")
}
}
Is is a correct approch ? as i am beginner in Reactjs
Here is a working version:
useHttpReqHandler.jsx
import { useState } from 'react';
import axios from "axios";
const useHttpReqHandler = () => {
const [apiResult, setApiResult] = useState();
const apiMethod = async ({url , data , method}) => {
let options = {
method,
url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
data
};
let response = await axios(options);
let responseOK = response && response.status === 200 && response.statusText === 'OK';
if (responseOK) {
const data = await response.data;
console.log(data)
setApiResult(data);
}
}
return [apiResult, apiMethod];
};
export default useHttpReqHandler;
What's important here:
await is called inside an async function (apiMethod)
The result is stored in a local state (apiResult)
The function returns an array [apiResult, apiMethod]
How to use it:
const [apiResult, apiMethod] = useHttpReqHandler();
apiMethod({url: 'some url here', data: {"email": email}, method: 'post'});
Render the result:
return {apiResult};
In my opinion, it is better to use .then with Axios. and try to create for each method different functions "Get/Post...", why because in the GET method you need to useEffect, but it can not be the same case in POST method. in GET method useHttpReqHandler.js
import { useEffect, useState } from "react";
import axios from "axios";
// GET DATA
const useHttpReqHandler = (url) => {
const [httpData, setHttpData] = useState();
useEffect(() => {
axios
.get(url)
.then((axiosData) => {
// Axios DATA object
setHttpData(axiosData.data);
// you can check what is in the object by console.log(axiosData);
// also you can change the state, call functions...
})
.catch((error) => {
console.log(error);
});
}, []);
return httpData;
};
export default useHttpReqHandler;
in your main file
import useHttpReqHandler from "...."
const MyFunc = () => {
const getData = useHttpReqHandler("URL");
return (
<div>
...
</div>
)
}
I hope it helps
the same thing will be with POSt, PUT, DELETE ... you will create functions for each method that will handle the Http req

Resources