dispatch redux action only if a previous action has success - reactjs

the situation is as follows: I need to call some API endpoints passing an auth token I previously requested. The problem is this token can expire, so each time I want to call an endpoint to retrieve data I need to ensure the token is valid and renew if necessary.
Currently I have an async action that renews the token and another set of actions that retrieves data from API.
My goal is to make some kind of aspect-programming/composition of both actions, like:
renewTokenIfNeeded = ... // action that check if token has expired and renew it
getData = ... // Retrieves data from API
realGetData = compose(renewTokenIfNeeded, getData);
The idea is realGetData action checks if token is expired, calling the renewTokenIfNeeded and only if this action success then call the getData.
Is there any pattern for this? Thanks.

I think you can create an action that returns a function instead of an object and use redux-thunk middleware. Actions that return a function are not pure and you can make more function calls and perform multiple dispatches within the action. So, it could look something like:
getData = () => async (dispatch, getState) => {
await renewTokenIfNeeded()
getData()
// Any dispatches you may want to do
}
Make sure you add the redux-thunk middleware to your store as mentioned in the documentation link.

You can use redux thunk or any other middleware (e.g redux saga) to solve the token issue. Redux thunk lets action creators return a function instead of an action.
export function checkTokenValidity(token) {
return (dispatch) => {
dispatch(TOKEN_CHECK_REQUEST());
return (AsyncAPICall)
.then((response) => {
dispatch(TOKEN_CHECK_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
export function getData(id) {
return (dispatch) => {
dispatch(GETDATA_REQUEST());
return (AsyncAPICall(id))
.then((response) => {
dispatch(GETDATA_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
export function checkTokenAndGetData(token,id) {
return (dispatch) => {
dispatch(checkTokenValidity(token)).then(() => {
dispatch(getData(id));
});
};
}
The problem with this approach is that code gets messy easily as you have to combine the checking of Token and an arbitrary API call every time.
This is a little better.
(2) (If you like to call it in your action creator for state tracking) Pass the token in the request, have a middleware to intercept the request and change the response to "Invalid Token/Expired Token", or the real data to have a clean and readable frontend code.
export function getData(token,id) {
return (dispatch) => {
dispatch(GETDATA_REQUEST());
return (AsyncAPICall(id))
.then((response) => {
//if response is EXPIRED token
dispatch(renewToken()); // renew token is another action creator
//else
dispatch(GETDATA_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
My suggestion, is to move your token validation in your backend.
(3)Always pass the token in the request, have a middleware to intercept the request, if it is expired, renew it in the backend code. Add an identifier to your response that it was expired and renewed it. Much simpler.

Related

Dispatch method in middleware not triggering reducer

Using the store method dispatch from the parameter provided by Redux Thunk middleware does not trigger the reducer. While using next() works properly as it triggers the reducer. Why is this happening?
middlerware
export default function createSlimAsyncMiddleware({
dispatch,
getState
}) {
return next => action => {
const {
types,
callAPI,
shouldCallAPI = () => true,
} = action;
if (!actionIsValid(action)) next(action);
if (shouldCallAPI(getState())) {
return Promise.resolve(getState());
}
const [pendingType, successType, errorType] = types;
dispatch({
type: pendingType
});
return callAPI()
.then(response => {
dispatch({ // Does not work, use next()
type: successType,
payload: response,
});
console.log('call resolved with type', successType)
return Promise.resolve(getState());
})
.catch(error => {
dispatch({ // Does not work, use next()
type: errorType,
payload: error,
});
return Promise.reject(error);
});
};
}
store
const store = createStore(
appReducer,
composeWithDevTools(applyMiddleware(
thunk,
createSlimAsyncMiddleware,
routerMiddleware(history)
))
)
Regarding this response https://stackoverflow.com/a/36160623/4428183 the dispatch should also work.
This is stated in the linked response you included, but calling dispatch() will create a new action, which then goes through the entire middleware chain from the beginning. In your case, this includes the middleware you're troubleshooting. From what I can see, the only time you call next() is in the case that an incoming action is deemed invalid. Otherwise, the subsequent API call results in dispatch() being called again whether the call succeeds or fails, and so the action never gets to the reducer because it's constantly being set at the beginning of your middleware chain and never gets to move along via next().
When you say this code doesn't work, what is the specific behavior? Does your app hang? Does it crash? Because this scenario essentially sets up a recursive function with no base case, I'd bet that you're seeing 'maximum call stack exceeded' sorts of errors.
I guess I'd ask why you need to use dispatch() for request results as opposed to sending them along using next(), or why you haven't set this up in a way that sets a conditional that uses the result of the previous call to determine whether the API gets called again.

React Redux wait for state change

I Want to extract all my server call functions (HTTP GET, POST, ...) into one helper function. The helper function will be called from different react components. It will check if the JWT Token is expired or not (client side) and if expired will ask user to relogin and get a new token before sending the request to the server.
Problem: When the helper function finds out the Token is expired it will dispatch an action to show the Login Dialog however it will continue the code calling the fetch. I need to wait for the Login Dialog to change the LoggedIn state and token before calling the server however it doesn't seem to be possible to watch for this state change. (One idea is returning a promise from Login dialog however I can't understand how to return a promise and where from!)
I appreciate that all the above can be very abstract and difficult to follow so have created a full code example showing what I need.
Sample Code
*PS : If the code sandbox fails please refresh it. They seem to be having some race issue with some of the plugins!
Is this what you are looking for?
componentDidUpdate(prevProps) {
if (!prevProps.loggedIn && this.props.loggedIn) {
// User just logged in
}
}
I am not a specialist of thunk yet, but what i can say is that you serverCall function must return a function with a dispatch parameter (see examples here)
You must dispatch an action in this sub function (in fact, call an action creator which will put the data in the application state.
You don't have to make an explicit promise because fetch return already a promise.
I will try something like :
export const serverCall = (
url,
method,
body = undefined,
successMessage = undefined
) => {
// your code
return function (dispatch) {
return fetch(url, {
method: method,
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
...(body && { body: JSON.stringify(body) })
}).then(response => response.JSON())
.then(response =>
if (response.ok) {
if (successMessage) {
console.log(successMessage);
}
dispatch(fetchData(response))
} else {
index.js
<Button
onClick={() =>
this.props.serverCall(
"https://jsonplaceholder.typicode.com/users",
"GET"
)
>
The state is to be removed here if you use Redux. All is taken from props via mapDispatchToProps.
const mapDispatchToProps = dispatch => ({
onLogin: (username, password) => dispatch(login(username, password)),
ToggleIsLoginDialogOpen: IsLoginDialogOpen =>
dispatch(toggleIsLoginDialogOpen(IsLoginDialogOpen)),
serverCall: (url, method) => dispatch(serverCall(url, method))
});

Redux axios request cancellation

I have a React Native application with Redux actions and reducers. I'm using the redux-thunk dispatch for waiting the asyncron calls. There is an action in my application:
export const getObjects = (id, page) => {
return (dispatch) => {
axios.get(`URL`)
.then(response => {
dispatch({ type: OBJECTS, payload: response });
}).catch(error => {
throw new Error(`Error: objects -> ${error}`);
});
};
};
That's working properly, but sometimes the user click on the back button before the action finished the request, and I must cancel it. How can I do it in a separated action? I read this, but I didn't find any option in axios for abort. I read about the axios cancellation, but it's create a cancel method on the function scope and I can't return, because the the JS don't support multiple returns.
What is the best way to cancel axios request in an other Redux action?
I would recommend using something like RxJS + Redux Observables which provides you with cancellable observables.
This solution requires a little bit of learning, but I believe it's a much more elegant way to handle asynchronous action dispatching versus redux-thunk which is only a partial solution to the problem.
I suggest watching Jay Phelps introduction video which may help you understand better the solution I'm about to propose.
A redux-observable epic enables you to dispatch actions to your store while using RxJS Observable functionalities. As you can see below the .takeUntil() operator lets you piggyback onto the ajax observable and stop it if elsewhere in your application the action MY_STOPPING_ACTION is dispatched which could be for instance a route change action that was dispatched by react-router-redux for example:
import { Observable } from 'rxjs';
const GET_OBJECTS = 'GET_OBJECTS';
const GET_OBJECTS_SUCCESS = 'GET_OBJECTS_SUCCESS';
const GET_OBJECTS_ERROR = 'GET_OBJECTS_ERROR';
const MY_STOPPING_ACTION = 'MY_STOPPING_ACTION';
function getObjects(id) {
return {
type: GET_OBJECTS,
id,
};
}
function getObjectsSuccess(data) {
return {
type: GET_OBJECTS_SUCCESS,
data,
};
}
function getObjectsError(error) {
return {
type: GET_OBJECTS_ERROR,
data,
};
}
const getObjectsEpic = (action$, store) = action$
.ofType(GET_OBJECTS)
.switchMap(action => Observable.ajax({
url: `http://example.com?id=${action.id}`,
})
.map(response => getObjectsSuccess(response))
.catch(error => getObjectsError(error))
.takeUntil(MY_STOPPING_ACTION)
);

Automatic handle 401 response with redux-saga and fetch

I'm building a "secured" application and using redux-saga together with fetchjs for doing the async calls to the backend.
My backend returns a 401 status code when the user is not authorized, i want to catch this "exception" globally and dispatch a action so my react application goes to the login screen.
I found the following solution: https://github.com/redux-saga/redux-saga/issues/110, but in this case the handling for the 401 should be explicit in every saga that we build.
By adding code to every saga it becomes more complex. It also increases the chances a developer forgets to add the code for handling the 401 code.
Is there a nice way to handle this 401 response globally?
I would not use redux-saga since it does not have ability to do what you require.
Instead, when setting up store, API layer and other things configure API layer do invoke handler on every error occurred.
Sample of API layer that reports invokes error handler.
const conf = {
onError: () => {},
}
api.onError = (cb) => {
conf.onError = cb;
}
api.get = (url) => {
fetch(url)
.then(response => {
if (response.ok === false) {
return conf.onError(response);
}
return response;
})
// Network error
.catch(conf.onError)
}
Configure application.
import store from './store';
// Configure your API wrapper and set error callback which will be called on every API error.
api.onError((error) => {
store.dispatch({
type: 'API_ERROR',
payload: error,
});
});
// In you reducers...
isAuthorized(state, action) {
if (action.type === 'API_ERROR' && action.payload.statusCode === 401) {
return false;
}
return state;
}
Then, call API as usually, If error occurs, action is dispatched to store and you may or may not react to this actions.

When does fetch() happen in Redux?

I'm having a problem with controlling the execution of my fetch() functions. Particularly I want to avoid letting the user spam fetch() requests.
My idea was to do this inside of middleware, but by the time the action with a fetch() gets there the payload is already a promise.
So my question is, when exactly does a fetch() already get executed?
If it matters, then my code looks roughly like this.
Parent action:
{
return (dispatch) => {
if (mode === 'MY') {
dispatch(myAction(...);
}
dispatch(someOtherAction(...));
}
}
My action:
{
type: 'TYPE',
promise: post(url, payload)
}
My post method:
{
console.log('sending POST');
return fetch(url, {
//fetch info
});
}
My middleware:
{
return next => action => {
const { promise, //other fields } = action;
//Already a promise here.
if (!promise) {
return next(action);
}
return promise.then(
//Processing data
);
};
}
My idea was to do this inside of middleware, but by the time the action with a fetch() gets there the payload is already a promise.
To solve this problem, I would do it as a check inside the action. So when you fire the fetch request, dispatch an action that a fetch is currently executing, update the state to say fetching = true. Then in any other action that needs to use fetch, check against that state and return nothing if it's true.
To answer your comment:
When you call an action creator, that is called immediately of course. When you call the dispatch, that will then execute the function (if using redux-thunk). Now, since redux-thunk is a middleware, it executes depending on order that you attached the middleware. So if you put your own middleware in the setup before redux thunk, it will execute before it.
const store = createStore(
combineReducers(reducers),
applyMiddleware(yourMiddleware, ReduxThunk)
);
Otherwise you have the understanding correct. An action fires immediately after calling dispatch, it'll go through the middleware in order, and then pass the action info to your reducers.

Resources