I want to model the following async logic using redux:
User action triggers a chain of async API calls.
Any API call might return 401 status (login timed out)
If API responds with 401, display re-login popup
On successful re-login, reissue API call and continue
I am not sure where to put this logic. Actions don't know about other actions, they only have access to dispatch, so they can't stop and wait for them to complete. Reducers don't have access to dispatch, so I can't put it there… so where does it live? Custom middleware? store.listen? In a smart component?
I'm currently using redux-promise-middleware & redux-thunk. How would one best organise this type of flow – without requiring buy-in into something like redux-saga or redux-rx, etc?
Also not sure best way to transparently interrupt the API call to perform those other actions i.e. API call shouldn't trigger its completed or failed actions until after the optional login process completes.
It sounds to me like you'd want an action creator that generates a Thunk, and keep all that logic in the Thunk. There's really no other good way to preserve the association between your suite of API calls, and ensure that all the others are cancelled if one fails.
In that Thunk, you'd fire your API calls, and collect their promises:
const call1 = promiseGenerator1();
const call2 = promiseGenerator2();
const call3 = promiseGenerator3();
const allCallPromises = [call1, call2, call3];
Use an all() promise handler to monitor them:
const watcher = Promise.all(allCallPromises).then(allSuccess, anyFail);
Your fail handler will:
cancel the rest of the promises if any of them 401's. (Note, this requires a library like Bluebird that has cancellation semantics, or some other form of augmentation of your promise/request.)
dispatch an action or route-change to trigger the re-login window
anyFail(error) => {
if (error.status === 401) {
allCallPromises.forEach((item)=> {item.cancel();});
reLogin();
}
}
Then, I'd be inclined to let your relogin component worry about re-firing that same complex action again, to issue all the calls.
However, should your suite of API calls be somehow variable or context-specific, you could cache on the store the ones you need, from inside the anyFail handler. Have a reducer where you can stash an actionPendingReLogin. Compose an action that will re-fire the same calls as last time, and then dispatch it:
dispatch(createAction('CACHE_RELOGIN_ACTION`, actionObjectToSaveForLater));
(Or, just cache whatever action-creator you used.)
Then, following successful relogin, you can:
const action = store.getState('actionPendingReLogin');
dispatch(action);
// or:
const actionCreator = store.getState('actionPendingReLogin');
dispatch(actionCreator());
Oh: and in your allSuccess handler you'd simply dispatch the results of the async calls.
Related
I am confused right now. Learning js frontend and how to make API requests.
Till now, whenever I made an API call, I always used something, that let me handle an asynchronous request (axios or thunk).
But now I got a case, where I am making a request to my Firebase/Firestore and in the yt video, that I watched, only useEffect was used. Without any async/await, like:
useEffect(() => {
// snapshot - to check, if there is a change (like a new entry) in the database
db.collection('products').onSnapshot((snapshot) => {
setProducts(snapshot.docs.map((doc) => doc.data()));
});
}, []);
Why is this?
In the example code snippet
useEffect(() => {
// snapshot - to check, if there is a change (like a new entry) in the database
db.collection("products").onSnapshot(snapshot => {
setProducts(snapshot.docs.map(doc => doc.data()))
})
}, []);
db.collection("products") manages its own asynchronous event handling. When a "snapshot" event is received it invokes the onSnapshot callback and updates state. There is nothing to await on, especially since there is no value returned.
You should also note that useEffect hook callbacks are 100% synchronous code, they can't be marked async and await any code calls. They can, however, call async functions that do their own awaiting and such.
About the questions in your title
Use a useEffect hook any time you want to issue any side-effect from the component lifecycle, like console logging state, make data fetches, dispatching actions. This is mostly due to the fact that the entire function body of a functional react component is to be considered a pure function. useEffect with dependencies is more akin to a class-based component's componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods. Alone they are just called as part of the react component lifecycle.
Use axios, or fetch, or any other data fetching code whenever you need to fetch that data.
useEffect() - It is a hook that allows us to perform side effects in functional components like network request etc.
Axios - It is just a library where we can make http request like fetch API in javascript and also axios is promise based.
I have an application which, depending on the state of the form, on submit, may have to make 0-3 API calls. 1 of those calls is dependent on what we can call the root call, such that if the root call is still resolving then we would need to wait for it to finish and return that data from the API before calling the dependent call. So:
Root call
Dependent call
Call 3
This is a React/Redux project. I could track which calls are in the process of "submitting" in a redux store and if the root call is "submitting" I could setup a setInterval to check if it's resolved before firing off the dependent call. Since this file is outside of a react component, to implement the aforementioned method, I would have to import the store and depend on the store.getState() method to check the state of the store which may have just been changed in a previous if block when the action called within it sends an update to the reducer. Would calling getState directly after firing off an async action reflect the correctly updated state?
As an alternative, what if I stored the most recent call in a global variable in the api file, and then naively chained a callback to it, like this:
api.js
let currentRootCall = Promise.resolve(null);
function setCurrentRootCall(call) {
currentRootCall = call;
}
export default function submit(values){
...
if (willSubmitRootCall) {
rootCall = doRootCall(values);
setCurrentRootCall(rootCall);
} else {
rootCall = currentRootCall;
}
...
if (willSubmitDependentCall) {
rootCall.then(() => doDependentCall(values));
}
if (willSubmit3rdCall) {
thirdCall = do3rdCall(values);
} else {
thirdCall = Promise.resolve(null);
}
...
return Promise.allSettled([rootCall, thirdCall])
.then(...) // fire off call completed actions for each of the 3 calls in the redux stores
}
This would save me the headache of polling the redux store to see if the call completed and then having to clean up the interval. It is my understanding that chaining to a resolved promise will just immediately fire off the chained callback. I feel as though I'm missing something here.
4 questions:
Is there a problem with this Promise chaining approach?
Will naively chaining to a possibly already resolved Promise cause
problems? How about with Promise.allSettled?
In the polling redux store method, would calling getState on the store directly after calling an async action reflect the updated state?
Is there a better way?
FYI: I am using axios library to make these calls.
I have a callback which I need to return true or false to trigger some external action, I need to make an API call inside this callback, so I need to get the state and dispatch some actions inside the callback, I don't know if I can use eventChannel because this callback could not be a generator, only a plain function. I need to do something like this.
zafClient.on('ticket.save', () => {
const state = yield select();
yield put(actionWithApiCall())
// I need to wait for the action to finish and then return something
// based on the response from the API
// I know how to block the saga to wait for the action dispatched
// the problem is that I can't use yield here
return somethingfromthestore;
});
Btw, this is zendesk API.
Your not going to be able to pass a generator function to that API. The work around is to dispatch an action directly to the redux store and then write a saga that listens for that action.
zafClient.on('ticket.save', () => reduxStore.dispatch(actionWithApiCall()))
You will have to make the redux store exportable from where you create it. So that you can directly access it here.
One of the challenges is how to yield in the callback. How about importing dispatch? Isn't dispatch the non-generator version of yield?
Should you be using React, doing so seems to be an option.
In the same saga that you use to specify the callback, you can subsequently listen for the action created in the dispatch/action creator callback. That's done by specifying another watchFor function (in my setup, any number of exported watchFor functions get added to the pool of saga watchers).
The paired saga worker can finalize whatever needs to happen to the data returned from the API call before using your final action creator to "document" the workflow by updating the store.
The other option might be to wrap the api call in an eventChannel (see: https://github.com/redux-saga/redux-saga/issues/1178).
- E
We provide a drop down option at the top..let's say it has options A B C.
Whenever user changes the drop down option, a saga gets triggered which makes around 10 different webapi calls.( A map of calls which get executed in parallel)
We use takeLatest helper in saga watcher.
So if user immediately changes the dropdown from A to B., Only calls made via B are consumed at client end as takeLatest aborts saga that got triggered by A.
Now , the problem is though only B's calls are consumed, all the A's promise calls that are in pending, also run to completion.
Only saga (because of A) gets aborted but not the api calls.
We can see that in network tab.
So, if user changes between A B C rather quickly, we would have around 30-40 calls to server.
All of those run to completion though we are just interested in completion of last 10 calls.
If we see in chrome dev tools, last 10 calls are in queue or stalled until the above ones are done.
Isn't there a way to propagate the cancellation from saga(takeLatest level)to axios and there by cancelling promises.
Axios docs say about cancellation and tokens but I'm not clear about how to propagate cancel in a redux -Saga and axios environment.
How to initiate cancel from saga to axios?
const cancelSource = axios.CancelToken.source()
try {
yield all([
call(axios.get, "/api/1", { cancelToken: cancelSource.token }),
call(axios.get, "/api/2", { cancelToken: cancelSource.token }),
/// ...
])
} finally {
if (yield cancelled()) {
yield call(cancelSource.cancel)
}
}
If someone is interested in a working solution to this exact problem, I am creator of redux saga library addon specifically to those kind of problems - https://github.com/klis87/redux-saga-requests
It does those kind of things automatically for you - if a saga in cancelled, request is aborted automatically, so you dont even need to think about it.
The top answer helped me, however, I had to use this call
yield call(cancelSource.cancel)
I am relatively newer to redux. I have gone through a lot of article still i am not getting a clear picture whats the real benefit of using redux-thunk.
All I understood is it allows you to return a function instead of object from action-creators? But what's the benefit? I have created few small react projects without using redux-thunk.
Let's consider the below snippets. Both behaves the same.
It would be great help if someone can explain me or point me to the correct resources to get a better understanding.
With redux-thunk
export function fetchContacts(){
return function(dispatch){
axios
.get('/contacts')
.then( contacts => dispatch({ type: 'FETCH_CONTACTS', payload: contacts}))
}
}
Without redux-thunk
const client = axios.create({
baseURL: "http://localhost:3000",
headers: {
"Content-Type": "application/json"
}
})
const url = '/contacts';
export function fetchContacts(){
return {
type: 'FETCH_CONTACTS',
payload: client.get(url)
}
}
The purpose of Redux itself, is to hold our application state. One of the great features of Redux is that we can change our state in a well-defined pattern and it's a pattern that we repeat over and over in our applications.
We call an Action Creator, this produces an Action. The Action flows into our Middleware, then our Reducers which then flows into our application State then into React. Then we sit around and wait for the user to initiate some change inside of our application that repeats the process all over again.
This process works fine with any kind of synchronous change. By synchronous I mean that we call an Action Creator which immediately flows into an Action, Middleware and our Reducers.
The vast majority of web applications we build, however, need to fetch data from asynchronous channels. In other words, its more common to call an Action Creator that is fetching data from an API or some asynchronous action and only when that request resolves are we actually ready to create an Action.
Vanilla Redux is not setup to handle this out of the box.
So, how do we handle these asynchronous Action Creators?
This is where Redux-Thunk comes into play. The purpose of Redux-Thunk is to give us direct control over the Dispatch method. The Dispatch method is part of the ReduxStore that contains our application state.
The Dispatch method handles:
Middleware
Reducers
State
When we normally call an Action Creator and it returns an Action, the Action ends up being returned into this Dispatch method. You have been using the Dispatch method behind-the-scenes in vanilla Redux.
So in practice, say you have a file in actions/index.js:
import axios from 'axios';
export function fetchUsers() {
const request = axios.get('http://somejsondata.com/users');
}
Redux expects us to return an action, but we do not yet have any data. I have to wait for my request to resolve before I have any data to send across to my Dispatch method.
So, lets use Redux-Thunk where all the existing rules for action creators kind of go out the window. Redux expects for us to return an Action which is a plain JavaScript object.
Redux-Thunk enables one other return type, which is a plain JavaScript function.
import axios from 'axios';
export function fetchUsers() {
const request = axios.get('http://somejsondata.com/users');
return () => {
};
}
The first argument is going to be the dispatch method:
import axios from 'axios';
export function fetchUsers() {
const request = axios.get('http://somejsondata.com/users');
return (dispatch) => {
};
}
If we pass an Action into dispatch, it's going to be sent off to all our different reducers.
export function fetchUsers() {
const request = axios.get('http://somejsondata.com/users');
return (dispatch) => {
request.then(({data}) => {
dispatch({type: 'FETCH_PROFILES', payload: data})
});
};
}
This is saying, we are going to wait for request to resolve with some amount of data and only when it has will I dispatch an action. In this case, it is going to have type FETCH_PROFILES and payload of data.
redux-thunk allows you to execute asynchronous operations.
In your first example you are sending the actual data returned by your API endpoint to your reducer. And the action will only be sent after the server has returned the data.
In your second example, you are sending a promise to your reducer which doesn't work as you would have to resolve your promise inside the reducer, which breaks the principle upon which reducers should be pure functions.
redux-thunk allows you to delay your actions in order to make async calls before dispatching. lets say you are retrieving user settings. the common use-case is to dispatch a REQUEST_FOR_USER_SETTINGS_IN_PROGRESS action so you can show a loader in your app, then make the http request for the data and when you get a response, dispatch a SUCCESS or ERROR action to update UI. it would look something like this:
const requestToGetCoins = await() => {
return (dispatch) => {
dispatch(requestToGetUserSettingsInProgress());
try{
const users = async httpService.getUserSettings();
dispatch(requestToGetUserSettingsSuccess(users));
}
catch(e){
dispatch(requestToGetUserSettingsError(e));
}
};
};
just wanted to emphasise that there is a better way to handle async actions in redux than redux-thunk, using an adhoc middleware which handles async actions and reduces much of the boilerplate. I suggest you take a look at this:
https://medium.com/#sht_5/minimal-code-for-redux-async-actions-c47ea85f2141