Best practice to handle redux + axios API request actions - reactjs

So my scenario is like this:
I have 2 types of requests: "public" requests which don't require authorization information, and "authorized" requests which do.
For authorized requests, before actually sending every request I must put auth info into the header. After the request finishes, the new access token returned from the server should be saved into the state.
So a lot of my actions currently looks like this:
export const asyncAction = (id) => async (dispatch, getState) => {
const { auth } = getState()
const config = {
headers: {
"access-token": auth["access-token"],
"client": auth.client,
"uid": auth.uid
}
}
const request = await axios.get(`${API_URL}`, config)
dispatch({ type: UPDATE_USER_ACCESS_TOKEN, payload: request})
dispatch({ type: ANOTHER_ACTION, payload: request.data })
}
I did some research and there are 2 possible ways to do this: Axios interceptor, or write a Redux middleware.
I feel like Axios interceptor, at least for the response part, is not the best solution here as it is intended to just do something with the response data, while I need to dispatch another action after I complete updating the new access-token, and I don't want to export the store to manually dispatch my actions, which is an anti-pattern. (as I mentioned the flow is: putting auth info into headers => send request => if it succeeds, then save the new access-token to Redux store for later use, then dispatch other actions; if it fails, do some error handlings).
Then I found this lib but I think I'm too dumb to understand the documentation:
https://github.com/svrcekmichal/redux-axios-middleware
Can someone please give me an example of how to do this? Both using that lib and writing custom middleware from scratch are fine for me. Thanks!

Related

Should I store a Axios instance configured with interceptors that use a state value in a state or ref or useMemo?

Similiar to Should we use axios inside both components and store?
I have a state called authorization which contains the Bearer token value that would be used in Axios calls. The state is available in a context and accessible using the useContext hook.
I create the AxiosInstance where I add a interceptors.request.use to add the Authorization header.
What I've done so far was useMemo with the authorization value a a dependency. But since Axios operation is asynchronous it seems that I may get the wrong axios instance.
I did a bit of refactoring using useRef and I still had a bit of an issue.
What I then did was implement the Observer pattern and send a notification to the component that provides the Axios client that the authorization header was changed and update the ref. However, again there's still some cases where the old client is being invoked.
What I am wondering is should I store it in useState or is there a fundamental problem with the approach of storing the Axios client and instead should I just bite the bullet and create a new axios client per request which takes the authorization header that's presently in the state?
The way I typically do it is to save the authentication information in a React context or to redux, and create axios instances as needed to access the authorization token.
Maybe something like:
const getBearerToken() => { ... [your implementation to retrieve from context here] ... };
const webserviceRequest = (url) => () => axios.create({
baseURL: url,
... [AxiosRequestConfig options here] ...
headers: {
Authorization: `Bearer ${getBearerToken()}`,
...
},
});
Then, you can define your webservice requests by invoking this function, e.g.:
const sampleGetRequest = () => webserviceRequest(SAMPLE_URL)().get('');
const samplePostRequest = (postData) => webserviceRequest(SAMPLE_URL)().post('', postData);
These return a Promise<AxiosResponse> which you can call as normal, e.g.
sampleGetRequest().then((response) => { ... }).catch((error) => { ... })
and so on.
The key point is that the webserviceRequest returns a function which creates an asynchronous webservice request with the current authorization token. You don't ever save the webserviceRequest function because that authorization token can become stale.
With the dependency on React context, I'd avoid using interceptors for this (I'd avoid using Axios all together but that's just my opinion).
Instead, try creating a custom hook
import axios from "axios";
import { useContext } from "react";
const api = axios.create({ /* baseURL, etc */ });
const useApi = () => {
const authorization = useContext(AuthContext); // guessing
if (authorization) {
api.defaults.headers.common.Authorization = `Bearer ${authorization}`;
} else {
delete api.defaults.headers.common.Authorization;
}
return api;
};

MirageJs: cannot use axios if I call api with passthrough

I have an app in react with a slice and a thunk.
I use #reduxjs/toolkit and I created slice with "createSlice" api and thunk with "createAsyncThunk".
My thunk:
export const loginThunk = createAsyncThunk('login/local', async (loginData: LoginData) => {
const {username, password} = loginData;
const l = await axios.post(`${BASE_URL}_login/local`, {username, password}, {
headers: {'Content-Type': 'application/json'}
})
return l.data;
})
In my app runs a mirage server with mock api and a "passthrough" at my true server.
When I dispatch "loginThunk" thunk, it runs the "loginThunk.pending" case in my reducer and stop.
Never it arrives to fulfilled or rejected.
If I dispatch "loginThunk" thunk, without mirage server running, it works.
If I dispatch "loginThunk" thunk, without mirage server running, but I use "fetch" instead axios, it works.
It seems is a problem between axios and mirageJs passthrough.
Any ideas??
Thank you so much
Turns out the problem stems from the xhr adapter that was changed in axios 0.21.2. The handler request.onreadystatechange is not added to the request anymore (XMLHttpRequest seems to be redefined when you start your mirageJS server).
The solution is to create your own adapter - basically copy lib/adapters/xhr.js and make sure that the else branch of the if statement goes through - and use it in your axios config.
I fix the issue downgrading axios to 0.21.0 and works with miragejs: 0.1.41, 0.1.42.0, 0.1.43.0

Redux, best way to access and use the state inside a middleware

I have a situation that I need to send two params in my post requests using axios to all my endpoints. These parameters are already in my store.
So, I create a "middleware" to procede all my requests like so:
const POST = async (url, body = {}) => {
const {Token, UserId} = store.getState().User
const {DeviceIdentifier} = await GetDeviceInfo()
const data = {AgentId: UserId, DeviceId: DeviceIdentifier, ...body}
const response = await axios.post(`${BASE_URL}${url}$`, data, {
headers: {Authorization: `Bearer ${Token}`},
})
if (!response.data.Logged) {
logoutUser()
return false
}
return response
}
But, I've been reading a lot of articles saying that using getState() like that is not a good idea...
What should be the best aproch in a situation like that?
Thank you so much!
When using redux thunk you can supply two arguments dispatch and getState. To supply more arguments:
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument({ url, body }))
)
// .withExtraArgument(url) // to supply further one arguments only
And then you can use getState like:
const POST = async (dispatch, getState, {url, body = {}}) => {
...
getState().User
Using getState() in a middleware is totally fine - that's why it's there!
If you'd like to read some extended discussion of some hypothetical concerns and my reasons why it's still okay, my post Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability goes into further details.

React + axios + redux interceptors

I have an React + redux + axios app, white jwt authentication. So, i need to intercept every request to server and set header with token. The question is where I have to set this headers, or implement interceptors. (also, i need redux store in scope, to get tokens from store). My idea - implement it in the Index component. Is it right way?
I suggest you to set the header axios.defaults.headers.common.authorization. Take a look here Global axios defaults. If you need a working example, this public repo can help you out.
Why do you have to manually set the header. Can't you just store the JWT in a cookie and then the browser will forward it automatically for you. Just make sure you pass credentials: 'include' in your HTTP options.
create a redux-middleware to do these things.
Apart from acting like interceptor to add header token,
you also do request/response transformation.
Also,you can mention the next action to which you want to dispatch the result if you don't want to return the promise and result.
Middleware gives you a chance to get the store state and also fetch & dispatch other action
const apiInterceptor = store => next => action => {
if(action.type !== 'ajax') return next(action)
const state = store.getState();
state.token // get token here
//add this api check part in some other module.
if(action.api=='UPDATE_CLIENT')
{
method = 'post';
url = 'some url';
}
else
{
method = 'get';
url = 'other url';
}
fetch(url, {
method: method,
headers : 'add token here',
body: JSON.stringify(action.body())
})
.then(response => response.json())
.then(json => json)//either return result
//OR dispatch the result
.then(json => {
dispatch({type:action.next_action,payload : json})
})
}
Integrate the middleware with store.
import customMiddleware from './customMiddleware'
const store = createStore(
reducer,
applyMiddleware(customMiddleware)
)
I offer you to refer redux-thunk.
You must create api-wrapper-helper to inject to redux-thunk as extra argument, then access to api-wrapper-helper from redux actions.
api-wrapper-helper is a function that get url and method as argument and send request to api-server then return result. (you can set headers in this file)
For example you can see ApiClient.js of react-redux-universal-hot-example boilerplate.
This is an old post but its getting a few views, so something like this would work nicely and is easily testable.
apiclient.js
import axios from 'axios';
import { setRequestHeadersInterceptor } from './interceptors';
const apiClient = axios.create(
{
baseUrl: 'https://my.api.com',
timeout: 3000
});
apiClient.interceptors.request.use(setRequestHeadersInterceptor);
export default apiClient;
interceptors.js
export const setRequestHeadersInterceptor = config =>
{
// have each interceptor do one thing - Single Responsibility Principle
};
you should store your auth details in a httpOnly secure cookie, the transmission to/from the server will be automatic then
// Interceptor
axios.interceptors.response.use(function (response) {
// success case here
return response
}, function (error) {
// Global Error Handling here
// showErrorToaster(error['message']);
return Promise.reject(error.response.data)
})

Redux: async actions without modifying the store

Is it a good idea to send http requests from redux-thunk middleware even if this http request doesn't modify the store?
Here is some code explaing what I mean:
export const CONFIRM_UPLOAD_REQUEST = 'CONFIRM_UPLOAD_REQUEST';
export const CONFIRM_UPLOAD_SUCCESS = 'CONFIRM_UPLOAD_SUCCESS';
export const CONFIRM_UPLOAD_FAILURE = 'CONFIRM_UPLOAD_FAILURE';
function _confirmUpload() {
return {
[CALL_API]: {
types: [CONFIRM_UPLOAD_REQUEST, CONFIRM_UPLOAD_SUCCESS, CONFIRM_UPLOAD_FAILURE],
requestMethod: 'POST',
endpoint: `upload/confirm`
}
};
}
export function confirmUpload() {
return (dispatch) => dispatch(_confirmUpload());
}
When I send this action, my middleware will execute POST upload/confirm request. It will not modify the store (so I don't have a reducer for CONFIRM_UPLOAD_SUCCESS).
The question: how bad is this approach? Must I execute http request directly (not via middleware)?
Answering on my own question, yes it's bad practice because the component will be re-rendered even if its properties were not changed

Resources