Use redux function after refresh token JWT - reactjs

I have functions
export function configureInterceptors(store) {
axios.interceptors.response.use(
response => response,
error => {
if (error.response && error.response.data) {
if (error.response.status === 401 && err.config) {
const originalRequest = error.config;
originalRequest._retry = true;
store.dispatch(jwtRefresh(originalRequest))
}
throw error;
}
}
);
}
export const jwtRefresh = (originalRequest) => dispatch => {
dispatch(jwtRefreshBegins());
axios
.post('auth/jwt/refresh/', {
refresh: window.localStorage.getItem('refresh')
})
.then(response => {
window.localStorage.setItem('jwt', response.data.access);
originalRequest.headers.Authorization = `JWT ${response.data.access}`;
return axios(originalRequest)
})
.catch(err => {
window.localStorage.removeItem('token');
window.localStorage.removeItem('jwt');
})
};
But all of my requests are in redux function. How can I make redux function again if refresh token was success? I must use redux function, because it change redux state.

Related

How do I get the HTTP response code from a successful React query?

How do I get the status code from a successful React query?
This is my custom hook:
const validateIban = async (accountId, encodedIban) => {
await axios
.post(`${CUSTOMER_PORTAL_API}/policy/accounts/${accountId}/iban/${encodedIban}`)
};
export function useValidateIban(accountId) {
return useMutation(encodedIban => validateIban(accountId, encodedIban));
}
And this is where I use the hook with mutate:
const validateIbanQuery = useValidateIban(accountId)
validateIbanQuery.mutate(encodeURIComponent(iban), {
onSuccess: () => {
******HERE I WANT THE STATUS CODE (204, 202 e.g.)******
},
onError: (error) => {
if (error.response.status === 400) {
....
}
if (error.response.status === 403) {
....
}
}
})
The first parameter of the onSuccess callback is the AxiosResponse:
axios.post("/api/data", { text }).then(response => {
console.log(response.status)
return response; // this response will be passed as the first parameter of onSuccess
});
onSuccess: (data) => {
console.log(data.status);
},
Live Demo

useContext inside axios interceptor

I cant figure out why my useContext is not being called in this function:
import { useContext } from "react";
import { MyContext } from "../contexts/MyContext.js";
import axios from "axios";
const baseURL = "...";
const axiosInstance = axios.create({
baseURL: baseURL,
timeout: 5000,
.
.
.
});
axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
const { setUser } = useContext(MyContext);
console.log("anything after this line is not running!!!!");
setUser(null)
.
.
.
My goal is to use an interceptor to check if the token is live and if its not clear the user and do the login. I'm using the same context in my other react components. And its working fine there, its just not running here! any idea whats I'm doing wrong?
I had the same issue as you. Here is how I solved it:
You can only use useContext inside a functional component which is why you can't execute setUser inside your axios interceptors.
What you can do though is to create a separate file called WithAxios:
// WithAxios.js
import { useContext, useEffect } from 'react'
import axios from 'axios'
const WithAxios = ({ children }) => {
const { setUser } = useContext(MyContext);
useEffect(() => {
axios.interceptors.response.use(response => response, async (error) => {
setUser(null)
})
}, [setUser])
return children
}
export default WithAxios
And then add WithAxios after MyContext.Provider to get access to your context like this for example:
// App.js
const App = () => {
const [user, setUser] = useState(initialState)
return (
<MyContext.Provider value={{ setUser }}>
<WithAxios>
{/* render the rest of your components here */}
</WithAxios>
</MyContext.Provider>
)
}
I don't have any issues catching the errors in this schema. are you catching them in the axios interceptor? here how I modified it:
useMemo(() => {
axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// Prevent infinite loops
if (
error.response.status === 401 &&
originalRequest.url === // your auth url ***
) {
handleLogout();
return Promise.reject(error);
}
if (
error.response.status === 401 &&
error.response.data.detail === "Token is invalid or expired"
) {
handleLogout(); // a function to handle logout (house keeping ... )
return Promise.reject(error);
}
if (
error.response.data.code === "token_not_valid" &&
error.response.status === 401 &&
error.response.statusText === "Unauthorized"
) {
const refreshToken = // get the refresh token from where you store
if (refreshToken && refreshToken !== "undefined") {
const tokenParts = JSON.parse(atob(refreshToken.split(".")[1]));
// exp date in token is expressed in seconds, while now() returns milliseconds:
const now = Math.ceil(Date.now() / 1000);
if (tokenParts.exp > now) {
try {
const response = await axiosInstance.post(
"***your auth url****",
{
//your refresh parameters
refresh: refreshToken,
}
);
// some internal stuff here ***
return axiosInstance(originalRequest);
} catch (err) {
console.log(err);
handleLogout();
}
} else {
console.log("Refresh token is expired", tokenParts.exp, now);
handleLogout();
}
} else {
console.log("Refresh token not available.");
handleLogout();
}
}
// specific error handling done elsewhere
return Promise.reject(error);
}
);
}, [setUser]);

Axios refresh token issue

I'm using React.useEffect() to retrieve the users list.
React.useEffect(() => {
dispatch(UsersActions.creators.fetchingUsersAction());
UsersApi.methods.getUsers().then(
(res) => {
dispatch(UsersActions.creators.fetchUsersSuccessAction(res.data));
},
(e) => {
dispatch(UsersActions.creators.fetchUsersErrorAction());
}
);
}, [dispatch]);
On this example, fetchingUsersAction is used to set "loading" to true, and fetchUsersErrorAction to false. This works fine, except when the request fails due to token expiration.
ApiClient.interceptors.response.use(
function (response) {
return response;
},
function (error) {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const refresh = JSON.stringify({
refreshToken: localStorage.getItem("refresh"),
});
AuthenticationApi.methods.refresh(refresh).then((res) => {
if (res.data.accessToken) {
localStorage.setItem("token", res.data.accessToken);
}
ApiClient.defaults.headers.common["Authorization"] =
"Bearer " + res.data.accessToken;
originalRequest.headers["Authorization"] =
"Bearer " + res.data.accessToken;
return ApiClient(originalRequest);
});
}
return Promise.reject(error);
}
);
This is sending a request to generate a new token and the previous request, but since the first request failed, the useEffect is going to the error section, making the "loading" false and showing the users list based on the previous state. What is the best way to deal with this problem?
Thanks
You should create an Async fucntion inside useEffect hook and use await to wait for the response, then call the function. Here is one example:
useEffect(() => {
const getRoles = async () => {
await authService.roles().then((res) => {
//Do your stuff.
console.log(res);
}).catch((error) => {
console.log(`'Catching the error: '${error}`);
});
};
//Call the recent created function.
getRoles();
}, []);
Your interceptor looks good to me.

React Redux Axios Api Get call with path parameters

I am trying to do a axios get call to my backend server with path variable and save the response data to the store. Unfortunately it only works for the get call with no path variables. still I can also log the response in the console, but I am unable to dispatch the response.data to the store
fetchByCardNumber: (CardNumber) => axios.get(baseURL+'loyalty/loyaltyCustomer/card/'+ CardNumber)
export const fetchByCardNumber = (CardNumber) => dispatch => {
LoyaltyAPI().fetchByCardNumber(CardNumber)
.then(response => {
if (response.status !== 200){
dispatch(Customer(null))
} else {
dispatch(Customer(response.data))
}
}).catch(error => {
return error;
})
}
But wherever I have used the url without pathVaribale in axios get call it works.
Eg : fetchCards: () => axios.get(baseURL+'loyalty/loyaltyCard/all')
While using the above URL I can dispatch the response to the store and get it.
//use it like that without curly braces between {cardNumber}
fetchByCardNumber: (CardNumber) => axios.get(baseURL+'loyalty/loyaltyCustomer/card/'+ CardNumber)
export const fetchByCardNumber = (CardNumber) => dispatch => {
LoyaltyAPI().fetchByCardNumber(CardNumber)
.then(response => {
if (response.status !== 200){
dispatch(Customer(null))
} else {
dispatch(Customer(response.data))
}
}).catch(error => {
return error;
})
}
The issue was with the way on dispatching the response to the store. The below code worked.
export const fetchByCardNumber = (CardNumber) => dispatch => {
LoyaltyAPI().fetchByCardNumber(CardNumber)
.then(response => {
if (response.status !== 200){
store.dispatch(SelectedCustomer(null))
} else {
store.dispatch(SelectedCustomer(response.data))
}
}).catch(error => {
return error;
})
}

Dispatching action from onUploadProgress event using Redux-Thunk / Axios

The following code uploads a file no problem and responds successfully or failing as expected, however, I cannot figure out how to dispatch my uploadFileProgress action from the onUploadProgress event. I can console.log the progress / percentage and when I try to wrap the dispatch in an IIFE, I trigger a dispatch is not a function error. Hopefully this is a small issue I'm missing. Thanks in advance!
export function uploadFile(values, callback = () => {}) {
const uploadFileData = new FormData();
uploadFileData.append('fileName', values.fileName);
uploadFileData.append('file', values.file);
uploadFileData.append('file', {
filename: values.filename,
contentType: values.contentType,
});
const uploadProgress = {
onUploadProgress: (ProgressEvent) => {
let progressData = 0;
const totalLength = ProgressEvent.lengthComputable ? ProgressEvent.total : ProgressEvent.target.getResponseHeader('content-length') || ProgressEvent.target.getResponseHeader('x-decompressed-content-length');
if (totalLength !== null) {
progressData = Math.round((ProgressEvent.loaded * 100) / totalLength);
}
return function action(dispatch) {
dispatch(uploadFileUpload(progressData));
};
},
};
const configPlusProgress = Object.assign(uploadProgress, config);
const request = () => axios.post(myURL, uploadFileData, configPlusProgress);
return function action(dispatch) {
dispatch(uploadFileLoading(true));
return request()
.then((response) => {
if (response.status !== 201) {
dispatch(uploadFileFail());
throw Error(response.statusText);
}
dispatch(uploadFileLoading(false));
return response;
})
.then(response => dispatch(uploadFileSuccess(response)))
.then(() => callback())
.catch(err => dispatch(uploadFileFail(err)));
};
}
move your request config inside returned function (where dispatch function will be accessible):
export function uploadFile(values, callback = () => {}) {
const uploadFileData = new FormData();
uploadFileData.append('fileName', values.fileName);
uploadFileData.append('file', values.file);
uploadFileData.append('file', {
filename: values.filename,
contentType: values.contentType,
});
return function action(dispatch) {
const uploadProgress = {
onUploadProgress: (ProgressEvent) => {
let progressData = 0;
const totalLength = ProgressEvent.lengthComputable ? ProgressEvent.total : ProgressEvent.target.getResponseHeader('content-length') || ProgressEvent.target.getResponseHeader('x-decompressed-content-length');
if (totalLength !== null) {
progressData = Math.round((ProgressEvent.loaded * 100) / totalLength);
}
dispatch(uploadFileUpload(progressData));
},
};
const configPlusProgress = Object.assign(uploadProgress, config);
const request = () => axios.post(myURL, uploadFileData, configPlusProgress);
dispatch(uploadFileLoading(true));
return request()
.then((response) => {
if (response.status !== 201) {
dispatch(uploadFileFail());
throw Error(response.statusText);
}
dispatch(uploadFileLoading(false));
return response;
})
.then(response => dispatch(uploadFileSuccess(response)))
.then(() => callback())
.catch(err => dispatch(uploadFileFail(err)));
};
}
Also onUploadProgress should just dipatch upload progress event.
I can't quite fix your code but here is a basic function with redux-thunk doing async stuff and using actions.
const doSomeAsyncStuff = () =>
async ( dispatch ) => {
try {
const response = await someAsyncStuff();
return dispatch( someSuccessAction( response.data );
} catch ( error ) {
return dispatch( someFailureAction( err );
}
}
Of course redux-thunk must be added as a middleware.
why are you returning a function from onUploadProgress function
return function action(dispatch) {
dispatch(uploadFileUpload(progressData));
};
Instead of that you can just
dispatch(uploadFileUpload(progressData));

Resources