I have a custom hook called Api which fetches data from my API & handles my auth token and refresh tokens.
On my Main app, there are multiple ways that my state variable "postId" will be changed. And whenever it is changed, I want my API to fetch the new content for that. But I can't call my custom Api within useEffect, which is how I'm detecting changes in postId.
Can someone please suggest a workaround? I spent forever making this API, now I feel like I can't even use it.
Main.tsx:
import React, {useState, useEffect} from 'react';
import Api from './hooks/useApi';
import Modal from 'react-modal'
import Vim from './Vim';
import './Main.css';
import './modal.css';
Modal.setAppElement('#root')
function Main():JSX.Element {
const [postId,updatePostId] = useState<number|null>(null)
const [content, updateContent] = useState<string>('default text');
const [auth, updateAuth] = useState<boolean>(false)
const [authModalIsOpen, setAuthModal] = useState(false)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [authKey, setAuthKey] = useState('')
const [refreshKey, setRefreshKey] = useState('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYxMjMzNjU4MiwianRpIjoiZTA0YjRlMjQ3MTI2NGY5ZWE4MWRiZjdiYmUzYzYwNzkiLCJ1c2VyX2lkIjoxfQ.TFBBqyZH8ZUtOLy3N-iwikXOLi2x_eKmdZuCVafPWgc')
const apiUrl = 'http://127.0.0.1:8000/'
function openAuthModal(){ setAuthModal(true) }
function closeAuthModal(){
if(auth){ setAuthModal(false) }
}
useEffect(()=>{
const props = {
username: 'raven',
password: 'asdfsdfds',
payload: {
path: 'notes/',
method: 'GET',
body: {pid: postId},
},
complete: (res:{})=>{console.log(res)},
fail: ()=>{}
}
Api(props)
},[postId])
function loadPost(pid:number):string|null{
// fetch from API, load post content
console.log('I can access:'+postId)
return null;
}
function backLinks():JSX.Element{
return(
<div className="backlinks">
</div>
)
}
function sendLogin(){
const requestOptions = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: username,
password: password
})
}
return fetch(apiUrl+'login', requestOptions)
.then(response=>response.json())
}
return (
<div className='main'>
<Vim key={postId} content={content} />
<Modal
isOpen={authModalIsOpen}
onRequestClose={closeAuthModal}
className='Modal'
overlayClassName='Overlay'
>
<form onSubmit={(e)=>{
e.preventDefault()
console.log(username)
sendLogin().then((data)=>{
if(data.auth){
updateAuth(true)
}
})
}}>
<input name='username' onChange={(e)=>{
setUsername(e.target.value)
}}/>
<input type="password" name='password' onChange={(e)=>{
setPassword(e.target.value)
}}/>
<button type="submit">Login</button>
</form>
</Modal>
</div>
)
}
export default Main
useApi.tsx:
import {useState, useEffect} from 'react'
interface IProps {
username:string,
password:string,
payload:IPayload,
complete: (result:{})=>void,
fail: ()=>void
}
interface IPayload {
path:string,
method:string,
body:{}|null,
}
function Api(props:IProps){
const [accessKey, setAccessKey] = useState('')
const [refreshKey, setRefreshKey] = useState('')
const [refreshKeyIsValid, setRefreshKeyIsValid] = useState<null|boolean>(null)
const apiUrl = 'http://127.0.0.1:8000/api/'
const [accessKeyIsValid, setAccessKeyIsValid] = useState<null|boolean>(null)
const [results, setResults] = useState<null|{}>(null)
function go(payload=props.payload){
const options = {
method: payload.method,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer '+accessKey,
},
... (payload.body !== null) && { body: JSON.stringify(payload.body) }
}
return fetch(apiUrl+payload.path,options)
.then(response=>{
if(response.status===401){
setAccessKeyIsValid(false)
return false
} else {
return response.json()
.then(response=>{
setResults(response)
return true
})
}
})
}
useEffect(()=>{
if(results){
props.complete(results)
}
},[results])
useEffect(()=>{
if(accessKeyIsValid===false){
// We tried to make a request, but our key is invalid.
// We need to use the refresh key
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json', },
body: JSON.stringify( {'refresh': refreshKey} ),
}
fetch(apiUrl+'token/refresh/', options)
.then(response=>{
if(response.status === 401){
setRefreshKeyIsValid(false)
// this needs to trigger a login event
} else {
response.json()
.then(response=>{
setRefreshKeyIsValid(true)
setAccessKey(response.access)
setRefreshKey(response.refresh)
setAccessKeyIsValid(true)
})
}
})
}
},[accessKeyIsValid])
useEffect(()=>{
if(accessKeyIsValid===true){
// Just refreshed with a new access key. Try our request again
go()
}
},[accessKeyIsValid])
useEffect(()=>{
if(refreshKeyIsValid===false){
// even after trying to login, the RK is invalid
// We must straight up log in.
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: props.username,
password: props.password,
})
}
fetch(apiUrl+'api/token/', options)
.then(response=>{
if(response.status === 401){ props.fail() }
else {
response.json()
.then(response=>{
setAccessKey(response.access)
setAccessKeyIsValid(true)
})
}
})
}
},[refreshKeyIsValid])
return( go() )
};
export default Api
You can pass dependencies to your custom hooks to be passed on to any underlying hooks that may depend on them. Since I'm not very familiar with Typescript there may be some necessary type definition tweaks. I've looked over your hook logic and suggest the follow for what I think would be the correct dependencies for when postId changes.
function useApi(props: IProps, deps) { // <-- accept a dependency array arg
const [accessKey, setAccessKey] = useState("");
const [refreshKey, setRefreshKey] = useState("");
const [refreshKeyIsValid, setRefreshKeyIsValid] = useState<null | boolean>(
null
);
const apiUrl = "http://127.0.0.1:8000/api/";
const [accessKeyIsValid, setAccessKeyIsValid] = useState<null | boolean>(
null
);
const [results, setResults] = useState<null | {}>(null);
const go = useCallback(() => { // <-- memoize go callback
const { body, method, path } = props.payload;
const options = {
method,
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + accessKey
},
...(body !== null && { body: JSON.stringify(body) })
};
return fetch(apiUrl + path, options).then((response) => {
if (response.status === 401) {
setAccessKeyIsValid(false);
return false;
} else {
return response.json().then((response) => {
setResults(response);
return true;
});
}
});
}, [accessKey, props.payload, setAccessKeyIsValid, setResults]);
useEffect(() => {
if (results) {
props.complete(results);
}
}, [results, props]);
useEffect(() => {
if (accessKeyIsValid) {
// Just refreshed with a new access key. Try our request again
go();
} else {
// We tried to make a request, but our key is invalid.
// We need to use the refresh key
const options = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh: refreshKey })
};
fetch(apiUrl + "token/refresh/", options).then((response) => {
if (response.status === 401) {
setRefreshKeyIsValid(false);
// this needs to trigger a login event
} else {
response.json().then((response) => {
setRefreshKeyIsValid(true);
setAccessKey(response.access);
setRefreshKey(response.refresh);
setAccessKeyIsValid(true);
});
}
});
}
}, [accessKeyIsValid, ...deps]); // <-- pass your dependencies
useEffect(() => {
if (!refreshKeyIsValid) {
// even after trying to login, the RK is invalid
// We must straight up log in.
const options = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username: props.username,
password: props.password
})
};
fetch(apiUrl + "api/token/", options).then((response) => {
if (response.status === 401) {
props.fail();
} else {
response.json().then((response) => {
setAccessKey(response.access);
setAccessKeyIsValid(true);
});
}
});
}
}, [refreshKeyIsValid, ...deps]); // <-- pass your dependencies
return go();
}
Usage
useApi(props, [postId]);
Related
I migrated and added Next.js to my React app. I getting the following error when I try to login. When I checked seems that I have to use promise.all. I tried different solutions without success. I want to know how it works. Your help and advice are highly appreciated.
error message;
Unhandled Runtime Error
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
src/action/auth.js;
import axios from 'axios';
import { setAlert } from './alert';
// import { API_URL } from '../config/index';
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
SIGNUP_SUCCESS,
SIGNUP_FAIL,
ACTIVATION_SUCCESS,
ACTIVATION_FAIL,
USER_LOADED_SUCCESS,
USER_LOADED_FAIL,
AUTHENTICATED_SUCCESS,
AUTHENTICATED_FAIL,
PASSWORD_RESET_SUCCESS,
PASSWORD_RESET_FAIL,
PASSWORD_RESET_CONFIRM_SUCCESS,
PASSWORD_RESET_CONFIRM_FAIL,
LOGOUT
} from './types';
export const checkAuthenticated = () => async dispatch => {
if (typeof window !== 'undefined' ? window.localStorage.getItem('access') : false) {
const config = {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
const body = JSON.stringify({ token: typeof window !== 'undefined' ? window.localStorage.getItem('access') : false });
try {
const res = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/auth/jwt/verify/`, body, config)
if (res.data.code !== 'token_not_valid') {
dispatch({
type: AUTHENTICATED_SUCCESS
});
} else {
dispatch({
type: AUTHENTICATED_FAIL
});
}
} catch (err) {
dispatch({
type: AUTHENTICATED_FAIL
});
}
} else {
dispatch({
type: AUTHENTICATED_FAIL
});
}
};
export const load_user = () => async dispatch => {
if (typeof window !== 'undefined' ? window.localStorage.getItem('access') : false) {
const config = {
headers: {
'Content-Type': 'application/json',
'Authorization': `JWT ${typeof window !== 'undefined' ? window.localStorage.getItem('access') : false}`,
'Accept': 'application/json'
}
};
try {
const res = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/auth/users/me/`, config);
dispatch({
type: USER_LOADED_SUCCESS,
payload: res.data
});
}catch (err) {
dispatch({
type: USER_LOADED_FAIL
});
}
} else {
dispatch({
type: USER_LOADED_FAIL
});
}
};
export const login = (email, password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/auth/jwt/create/`, body, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
});
dispatch(setAlert('Authenticated successfully', 'success'));
dispatch(load_user());
}catch (err) {
dispatch({
type: LOGIN_FAIL
});
dispatch(setAlert('Error Authenticating', 'error'));
}
};
export const signup = (name, email, password, re_password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ name, email, password, re_password });
try {
const res = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/auth/users/`, body, config);
dispatch({
type: SIGNUP_SUCCESS,
payload: res.data
});
dispatch(setAlert('Check Your Email to Activate Your Account.', 'warning'));
} catch (err) {
dispatch({
type: SIGNUP_FAIL
})
}
};
export const verify = (uid, token) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ uid, token });
try {
await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/auth/users/activation/`, body, config);
dispatch({
type: ACTIVATION_SUCCESS,
});
dispatch(setAlert('Account Activated Successfully.', 'success'));
} catch (err) {
dispatch({
type: ACTIVATION_FAIL
})
}
};
//Reset Password
export const reset_password = (email) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email });
try {
await axios.post (`${process.env.NEXT_PUBLIC_API_URL}/auth/users/reset_password/`, body, config);
dispatch({
type: PASSWORD_RESET_SUCCESS
});
dispatch(setAlert('Check Your Email to Rest Password.', 'warning'));
} catch (err) {
dispatch({
type: PASSWORD_RESET_FAIL
});
}
};
// Reset Password Confirm
export const reset_password_confirm = (uid, token, new_password, re_new_password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ uid, token, new_password, re_new_password });
try {
await axios.post (`${process.env.NEXT_PUBLIC_API_URL}/auth/users/reset_password_confirm/`, body, config);
dispatch(setAlert('Password Rest Successful.', 'success'));
dispatch({
type: PASSWORD_RESET_CONFIRM_SUCCESS
});
} catch (err) {
dispatch({
type: PASSWORD_RESET_CONFIRM_FAIL
});
}
};
//Logout
export const logout = () => dispatch => {
dispatch(setAlert('Logout successful.', 'success'));
dispatch({
type: LOGOUT
});
};
src/pages/login.js;
import React, { useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { connect } from 'react-redux';
import { Button } from '#mui/material';
import { login } from '../actions/auth';
import styles from '../styles/Login.module.css';
import Head from 'next/head';
import WelcomePageFooter from '../components/WelcomePageFooter';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next';
import i18n from '../../i18n';
function Login({ login, isAuthenticated }) {
const { t } = useTranslation();
const navigate = useRouter();
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value});
const onSubmit = e => {
e.preventDefault();
login (email, password)
};
if (isAuthenticated) {
return (
navigate.replace('/')
);
}
return (
<main>
<div className={styles.login}>
<Head>
<title>Diploman - Login</title>
<meta
name='description'
content='login page'
/>
</Head>
<h1 className={styles.login__title}>{t('login_title')}</h1>
<p className={styles.login__lead}>{t('login_lead')}</p>
<form className={styles.login__form} onSubmit={e => onSubmit(e)}>
<div className={styles.login__form__group}>
<input
className={styles.login__form__input}
type='email'
placeholder={t('Form_email')}
name='email'
value={email}
onChange={e => onChange(e)}
required
/>
</div>
<div className={styles.login__form__group}>
<input
className={styles.login__form__input}
type='password'
placeholder={t('Form_pw')}
name='password'
value={password}
onChange={e => onChange(e)}
minLength='8'
required
/>
</div>
<Button className={styles.login__button__main} type='submit'>{t('login_title')}</Button>
</form>
<p className={styles.link__to__Signup}>
{t('login_text1')} <Link href='/signup' className={styles.login__link}>{t('login_register')}</Link>
</p>
<p className={styles.link__to__resetPassword}>
{t('login_text2')} <Link href='/reset-password' className={styles.reset__password__link}>{t('login_reset')}</Link>
</p>
</div>
<WelcomePageFooter/>
</main>
)
};
export const getServerSideProps = async ({ locale }) => (
{ props: {
...(await serverSideTranslations(
locale,
['common'],
i18n,
)),
} }
);
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect (mapStateToProps, { login }) (Login);
I really appreciate your help here
here i have bit problem with my custom useFetch
i want to call the token first and the fetch code function later. I was tried in the useEffect this way
its creating the token firstly and queryCall(tokenKey) function still taking the null token
value if the valid token available
here is the my logic in useEffect
React.useEffect(() => {
if (!tokenKey) {
const getToken = () => {
tokenKey = ApplicationToken(true);
queryCall(tokenKey);
};
getToken();
} else {
queryCall(tokenKey);
}
}, [query]);
here is the ApplicationToken function is responsible for creating new token based on the useEffect condition (!tokenKey)
ApplicationToken.js
import qs from 'qs';
import axios from 'axios';
const ApplicationToken = IsError => {
// eslint-disable-next-line no-undef
const Grant = window?._env_?.REACT_APP_GRANT;
// eslint-disable-next-line no-undef
const Client = window?._env_?.REACT_APP_CLIENT;
// eslint-disable-next-line no-undef
const Key = window?._env_?.REACT_APP_KEY;
// eslint-disable-next-line no-undef
const Auth = window?._env_?.REACT_APP_AUTH;
// eslint-disable-next-line no-undef
if (IsError || window.localStorage.getItem('applicationToken') === null) {
let data = qs.stringify({
grant_type: `${Grant}`,
client_id: `${Client}`,
client_secret: `${Key}`,
});
let config = {
method: 'POST',
url: `${Auth}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
data: data,
};
axios(config)
.then(response => {
window.localStorage.setItem(
'applicationToken',
JSON.stringify(response)
);
})
.catch(function (error) {
console.log(error);
});
return JSON.parse(window.localStorage.getItem('applicationToken'));
} else {
return JSON.parse(window.localStorage.getItem('applicationToken'));
}
};
export default ApplicationToken;
Im storing the access token in local storage
here is the useFetch.js
import React from 'react';
import qs from 'qs';
import axios from 'axios';
import ApplicationToken from './ApplicationToken';
var tokenKey = ApplicationToken(false);
const useFetch = query => {
const [status, setStatus] = React.useState('idle');
const [result, setResult] = React.useState([]);
const newResult = query.substituteDataValue.toLowerCase();
var data = qs.stringify({
query: `${query.api}`,
});
// eslint-disable-next-line no-undef
const Query = window?._env_?.REACT_APP_QUERY;
const queryCall = React.useCallback(
async token => {
if (!token) {
return;
} else {
setStatus('Loading');
var config = {
method: 'POST',
url: `${Query}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization:
token?.data?.token_type + ' ' + token?.data?.access_token,
},
data: data,
};
setStatus('Loading');
axios(config)
.then(response => {
// console.log(response.data.results.bindings);
setStatus('success');
setResult(response.data.results.bindings);
})
.catch(function (error) {
setStatus('error');
if (error.response.status === 401) {
tokenKey = ApplicationToken(true);
queryCall(tokenKey);
}
});
}
},
[data]
);
React.useEffect(() => {
if (!tokenKey) {
tokenKey = ApplicationToken(true);
queryCall(tokenKey);
} else {
queryCall(tokenKey);
}
}, [query]);
return [status, result];
};
export default useFetch;
i want the output process like this
step-1.create newToken if existing token fail
step-2. call the queryCall(tokenKey) with updated token tokenKey
1 & 2 are in useEffect only in my code
when im doing it, the firstStep is working fine but second step is working with older token
so the effect of result is useFetch file if(!token) return;
how does queryCall() function wait until newToken created in useEffect ?
I want to try and use react to fetch data efficiently using useEffect appropriately.
Currently, data fetching is happening constantly, instead of just once as is needed, and changing when there is a input to the date period (calling different data).
The component is like this,
export default function Analytics() {
const {
sentimentData,
expressionsData,
overall,
handleChange,
startDate,
endDate,
sentimentStatistical,
} = useAnalytics();
return (
UseAnalytics is another component specifically for fetching data, basically just a series of fetches.
i.e.,
export default function useAnalytics() {
....
const { data: sentimentData } = useSWR(
`dashboard/sentiment/get-sentiment-timefilter?startTime=${startDate}&endTime=${endDate}`,
fetchSentiment
);
....
return {
sentimentData,
expressionsData,
overall,
handleChange,
setDateRange,
sentimentStatistical,
startDate,
endDate,
};
}
Thanks in advance,
The apirequest is like this,
export async function apiRequest(path, method = "GET", data) {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
//this is a workaround due to the backend responses not being built for this util.
if (path == "dashboard/get-settings") {
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.error === "error") {
throw new CustomError(response.code, response.messages);
} else {
return response;
}
});
}
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.status === "error") {
// Automatically signout user if accessToken is no longer valid
if (response.code === "auth/invalid-user-token") {
firebase.auth().signOut();
}
throw new CustomError(response.code, response.message);
} else {
return response.data;
}
});
}
I think using useEffect here is the right approach. i.e.,
useEffect(()=>{
// this callback function gets called when there is some change in the
// state variable (present in the dependency array)
},[state variable])
I'm confused about how to update the constants properly, something like this seems like one approach, but not sure about how I can use useEffect to update these variables properly, or if I should be doing this inside of useAnalytics?
i.e.,
const [analytics, setAnalytics] = useState({
sentimentData: {},
expressionsData: {},
overall: {},
handleChange: () => {},
startDate: '',
endDate: '',
sentimentStatistical:{},
});
useEffect(()=>{
// this callback function gets called when there is some change in the
// state variable (present in the dependency array)
},[state variable])
const {
sentimentData,
expressionsData,
overall,
handleChange,
startDate,
endDate,
sentimentStatistical,
} = useAnalytics();
Realised SWR is a hook, need to use SWR documentation :P
You have to store the requested information in states inside your custom hook. Then you could consume this hook wherever you want. This should work.
Define custom hook
const useAnalitycs = () => {
const [analytics, setAnalytics] = useState({
sentimentData: {},
expressionsData: {},
overall: {},
startDate: '',
endDate: '',
sentimentStatistical:{},
});
const handleChange = () => {
/* */
};
useEffect(() => {
const fetchData = async () => {
// const response = await apiCall();
// setAnalytics(...)
};
fetchData();
}, []); // called once
return {
...analytics,
handleChange
};
};
Consume useAnalytics hook
const ComponentConsumerA = () => {
/*
const { state/methods you need } = useAnalytics()
...
*/
};
const ComponentConsumerB = () => {
/*
const { state/methods you need } = useAnalytics()
...
*/
};
I'm really new to react and I have this
import Axios from "axios";
import { useAuth } from "react-oidc-context";
const ProductService = {
getProductList: () => {
return Axios({
method: "get",
url: "<myurl>",
headers: {
"Authorization": useAuth().user?.access_token
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
},
getProduct: (productId: string) => {
return Axios({
method: "get",
url: "<myurl>/" + productId,
headers: {
"Authorization": useAuth().user?.access_token
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
},
addClient: (data: any) => {
return Axios({
method: "post",
url: "<myurl>",
data: data,
headers: {
"Authorization": useAuth().user?.access_token
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
}
}
export default ProductService
Notice that I'm trying to use useAuth() in the Authorization header and I'm getting React Hook "useAuth" is called in function "getProductList" which is neither a React function component or a custom React Hook function.
In this case, what's the workaround so I can use useAuth() to get user token.
My Component
<Button type="submit"
onClick={() => {
ProductService.addClient(data)
.then(() => {
toggleModal();
});
}}>
Add
</Button>
Thanks
Hooks is a function that controls state management or life cycle methods of the React component through registered order. So, React Hooks are not available outside the component. Please refer to the Link.
Only Call Hooks at the Top Level. So, the getProductList should be changed as follows.
const getProductList = (access_token) => {
if (!access_token) throw new Error('No access_token');
return Axios({
method: "get",
url: "<myurl>",
headers: {
"Authorization": access_token
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
};
const YourReactComponent = () => {
const auth = useAuth();
useEffect(() => {
getProductList(auth?.user?.access_token)
.then(() => {
/* NEXT STEP */
})
}, [auth?.user?.access_token]);
return <>
Component Text.
</>
};
As per Hooks rule, we can use hooks only from React function components or custom Hooks.
In your scenario,
Create one React component.
Get value from "useAuth()" in above functional component.
Pass above the value to ProductService.getProductList(auth) as one of the parameter.
I hope you are calling ProductService from particular react component right. Get auth value from there and pass it to ProductService.getProductList(auth)
const ProductService = {
getProductList: (authToken: any) => {
return Axios({
method: "get",
url: "<myurl>",
headers: {
"Authorization": authToken
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
},
getProduct: (authToken: any, productId: string) => {
return Axios({
method: "get",
url: "<myurl>/" + productId,
headers: {
"Authorization": authToken
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
},
addClient: (authToken: any, data: any) => {
return Axios({
method: "post",
url: "<myurl>",
data: data,
headers: {
"Authorization": authToken
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
}
}
const TestReactFunctionalComponent = () => {
const auth = useAuth();
// use below calling wherever you want inside this component
ProductService.getProductList(auth.user?.access_token);
return(
// your compoent elements
)
};
I'm trying to implement refresh-token from react. I'm using this library axios-auth-refresh which seems to work very fine except for one API.
// api.js
import Axios from "axios";
import Cookies from 'js-cookie'
import { TOKEN_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME } from '../constants/constants';
import createAuthRefreshInterceptor from 'axios-auth-refresh';
const api = Axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
const refreshAuthLogic = async (failedRequest) => {
const refresh_token = Cookies.get(REFRESH_TOKEN_COOKIE_NAME);
// if(!refresh_token) return;
console.log(refresh_token);
const tokenRefreshResponse = await api.post('auth/createtoken', {
}, {
headers: {'Authorization': 'Bearer ' + refresh_token},
validateStatus: () => true
});
console.log(tokenRefreshResponse);
if(tokenRefreshResponse.data.statusCode === 401 || tokenRefreshResponse.data.statusCode === 403) {
Cookies.remove(REFRESH_TOKEN_COOKIE_NAME);
if(!window.location.href.includes('login')) {
window.location.href = "http://localhost:3000/login";
}
return;
}
const access_token = tokenRefreshResponse.data.access_token;
Cookies.set(TOKEN_COOKIE_NAME, access_token, { expires: 60 })
api.defaults.headers.Authorization = `Bearer ${access_token}`
failedRequest.response.config.headers['Authorization'] = 'Bearer ' + access_token;
}
// Instantiate the interceptor (you can chain it as it returns the axios instance)
createAuthRefreshInterceptor(api, refreshAuthLogic);
export default api;
The following api call does NOT repeat in case 401 is returned:
const fetchUsers = async () => {
const { data } = await api.get(`users/`, {params: {tripUsers: true}}, {
validateStatus: (status) => status !== 401 && status !== 403
})
setUsers(data);
}
useEffect(() => {
fetchUsers();
}, [])
The following api call DOES repeat in case 401 is returned:
const fetchProfile = async () => {
const { data } = await api.get(`/users/${user.userId}`, {}, {
validateStatus: (status) => status !== 401 && status !== 403
})
const {statusCode, message} = data;
console.log(data);
if(!statusCode) {
console.log(data);
setState(data);
}
}
useEffect(() => {
fetchProfile();
}, [])
Please help.
After spending some time on this issue, I decided to create a generic API caller rather than using axios interceptors or any other library. Here's my generic axios API caller. It can still be improved, but the idea is to call the API again with a new token if the first token is expired.
// api.js
import Axios from "axios";
import Cookies from 'js-cookie'
import { TOKEN_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME } from '../constants/constants';
const api = Axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
export const callApi = async (method, url, params, other) => {
const validateStatus =
url === 'auth/login' ? () => true : (status) => status !== 401 && status !== 403
const options = {
url,
method,
validateStatus,
...other
}
options[method === 'GET' ? 'params' : 'data'] = params;
console.log(options);
try {
const data = await api(options);
return Promise.resolve(data);
} catch (err) {
console.log(err.response.status);
if (err && err.response && err.response.status === 401) {
return performTokenRefresh(options);
} else {
return Promise.reject(err);
}
}
};
const performTokenRefresh = async (options) => {
const refresh_token = Cookies.get(REFRESH_TOKEN_COOKIE_NAME);
if(!refresh_token) return {};
const tokenRefreshResponse = await api.post('auth/createtoken', {
}, {
headers: {'Authorization': 'Bearer ' + refresh_token},
validateStatus: () => true
});
if(tokenRefreshResponse.data.statusCode === 401 || tokenRefreshResponse.data.statusCode === 403) {
Cookies.remove(REFRESH_TOKEN_COOKIE_NAME);
if(!window.location.href.includes('login')) {
window.location.href = "http://localhost:3000/login";
}
return {};
}
const access_token = tokenRefreshResponse.data.access_token;
Cookies.set(TOKEN_COOKIE_NAME, access_token, { expires: 60 })
api.defaults.headers.Authorization = `Bearer ${access_token}`
return api(options);
}
export default api;