How to wait 'return' until 'dispatch' finish - reactjs

[React JS]How to wait 'return' until 'dispatch' finish in action creator?
I don't know how handle this ;(
i made some code. this is a part of action creator. But it return before dispatch finish. i want to dispatch finish before 'return'. help me please
export const Hello = param=> dispatch => {
return postApi('/hello', param)
.then(async res => {
await dispatch(goHello(res));
return true;
})

By default, there really is no need to call return after dispatching an action. But if you'd like, you could use the getState() method to check that your action was processed before returning. getState() is the second argument of the redux-thunk function you are returning in your action-creator.
export const Hello = (param) => async (dispatch, getState) => {
postApi("/hello", param)
.then((res) => {
await dispatch(goHello(res))
})
.catch((err) => {
console.log(err.response.data)
})
//check if reducer was updated
const value = getState().keyOfReducerInStore <-- returns the state of your reducer, you can follow this up with whatever value you want to check
if(value){
return true
}
}

You can use await on postApi. You can only use await directly inside of an async function. Link.
export const Hello = (param) => async (dispatch, getState) => {
const res = await postApi("/hello", param); // <- code pauses here
const res2 = await anotherPostApi("/helloagain", param); // <- will wait for above
dispatch(goHello(res));
// You can also wrap the await postApi in a try catch instead of using .catch
const value = getState().keyOfReducerInStore <-- returns the state of your reducer, you can follow this up with whatever value you want to check
if(value){
return true
}
}
Just remember. Promises can be awaited as long as they are directly inside of an async function. If you console.log a variable it'll show you if it is a Promise.

Related

can't I dispatch actions in sequence?

I'm trying to display a loading spinner im my react app when a user is clicking on 'save'.
I'd like to dispatch an action that will update my redux store as loading to true and as false when the api call is finished.
The thing is i'm currently using middleware that handles all the code related to api calls. Whenever this code is running, is seems to always be 'false', maybe because the dispatch actions are batched ?
So should I have this dispatch loading action in the middleware or not necessarily ?
Thanks !
const handleSave = () => {
dispatch(isLoading(true))
dispatch(updateProfileImage());
dispatch(isLoading(false))
};
I assume you use thunk, you can combine thunks in thunks and wait for thunks if it returns a promise, here is a pseudo example:
const doThisThunk = () => (dispatch) => {
dispatch(loadingThis(true));
//returning a promise here
return (
asyncStuff()
.then(
(resolve) => dispatch(updateThis(resolve)),
(error) => dispatch(errorThis(error))
)
//no matter resolve or reject, set loading false
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
.finally(() => dispatch(loadingThis(false)))
);
};
const doThatThunk = () => (dispatch) => {
dispatch(loadingThat(true));
//returning a promise here
return (
asyncStuff()
.then(
(resolve) => dispatch(updateThat(resolve)),
(error) => dispatch(errorThat(error))
)
.finally(() => dispatch(loadingThat(false)))
);
};
const doThisThenDoThatThunk = () => (dispatch) =>
//bodiless function automatically returns return value
// of the next line
dispatch(doThisThunk()).then(() => dispatch(doThatThunk()));
The above example also shows how you set loading logic in your thunk.

Dispatch action on the createAsyncThunk?

I hava a thunk action was created by createAsyncThunk. I want to dispatch an action before call api to update state.
I don't want use action getProducts.pending because I want dispatch actionLoading() for other thunk actions.
How I can i do it? Thanks!
export const getProducts = createAsyncThunk("getProducts", async () => {
// I want dispatch action actionCallAPIPending like that
// dispatch(actionLoading());
const response = await axios.get("api");
return response;
});
You can do it like this with the second param of callback function in the createAsyncThunk:
export const getProducts = createAsyncThunk("getProducts", async (_, thunkAPI) => {
thunkAPI.dispatch(actionLoading());
const response = await axios.get("api");
return response;
});
As the createStore method got deprecated I was looking for a solution to migrate an existing project to use #reduxjs/toolkit and in the same time to set up a new one.
I found #Viet's
answer very useful, although to be complete I would like to mention another way of using Async Thunks just to have it in the same thread for people who end up here and might would find it useful.
You still can create an Async Thunk without createAsyncThunk, but this way you cannot rely on the extraReducers as a downside.
export const getProducts = (): AppThunk => async (dispatch) => {
dispatch(actionLoading());
try {
const response = await axios.get("api");
// return the response or store it
return response;
} catch (error) {
// handle the error
console.warn(error)
}
dispatch(actionLoading());
};

Is it possible to await for dispatch is finish

I have 2 actions, and I call from the first action to the second action .... and I need to wait until the second action is finished and only then continue with the action.
// first action
export const getDataFromAdmin = () => {
return async (dispatch, getState) => {
dispatch(getDeviceLocation());
console.log('only after getDeviceLocation is finsih');
AdminRepository.getDataFromAdminAndEnums(dispatch)
.then(adminData => {
//some code
})
.catch(error => {
console.log(`Splash Error = ${error.message}`);
});
};
};
//second action
export const getDeviceLocation = () => {
return async dispatch => {
dispatch({ type: actionsType.GET_DEVICE_LOCATION });
LocationManager.getCurrentPosition()
.then(location => {
dispatch({ type: actionsType.GET_DEVICE_LOCATION_SUCCESS });
})
.catch(error => {
dispatch({ type: actionsType.GET_DEVICE_LOCATION_ERROR, message: error.message });
});
};
};
No, it's not possible because dispatching an action is something like triggering action and action just invoke either middleware or reducer that's it. The action doesn't wait to complete the reducer or middleware. It just invokes and finishes his job.
Ok, so in the end I make async await by pass dispatch as a parmeter to the function.
Yes you can await dispatch, but only if you are using redux-thunk middleware.
dispatch is a synchronous action by default. redux-thunk middleware allows you to dispatch actions which are a function of dispatch, like the actions in your question, in addition to the standard { type: 'SOME_NAME' } action objects. redux-thunk also makes it so that dispatching these "thunk" actions is asyncronous. This allows you to use aysnc/await or Promise.then() when you call dispatch(myThunkAction());
async function someFunction() {
await dispatch(myThunkAction());
doSomethingElse();
}
dispatch(myThunkAction()).then(() => {
doSomethingElse();
});
Here is an example from the redux-thunk documentation:
// In fact I can write action creators that dispatch
// actions and async actions from other action creators,
// and I can build my control flow with Promises.
function makeSandwichesForEverybody() {
return function (dispatch, getState) {
if (!getState().sandwiches.isShopOpen) {
// You don’t have to return Promises, but it’s a handy convention
// so the caller can always call .then() on async dispatch result.
return Promise.resolve()
}
// We can dispatch both plain object actions and other thunks,
// which lets us compose the asynchronous actions in a single flow.
return dispatch(makeASandwichWithSecretSauce('My Grandma'))
.then(() =>
Promise.all([
dispatch(makeASandwichWithSecretSauce('Me')),
dispatch(makeASandwichWithSecretSauce('My wife'))
])
)
.then(() => dispatch(makeASandwichWithSecretSauce('Our kids')))
.then(() =>
dispatch(
getState().myMoney > 42
? withdrawMoney(42)
: apologize('Me', 'The Sandwich Shop')
)
)
}
}
You can view the complete code in the redux-thunk docs section on Composition to see how they define the makeASandwichWithSecretSauce thunk.
The resolved value of the Promise will be whatever you return in your myThunkAction function. Typically thunks will call dispatch and will not return anything, so this feature is rarely used.
Any return value from the inner function will be available as the return value of dispatch itself. This is convenient for orchestrating an asynchronous control flow with thunk action creators dispatching each other and returning Promises to wait for each other’s completion. (source)
async function someFunction() {
const result = await dispatch(myThunkAction());
doSomethingWithResult(result);
}
dispatch(myThunkAction()).then((result) => {
doSomethingWithResult(result);
});

Get data from a promise. Or is it promise at all?

I store some data in IndexedDB and i use npm package localforage for it.
const retrieveData = async () => {
const keys = await localforage.keys()
const data = await keys.map(async (key) => {
const item = await localforage.getItem(key)
return [item.username, item.compamy, item.email, item.lastUpdate]
})
return data
}
Whenever I execute this function, I get a resolved Promise object, which values I cannot extract
async componentDidMount() {
let asyncData = retrieveData()
console.log(asyncData) // Promise object
asyncData = retrieveData().then(values => values)
console.log(asyncData) // Promise object anyways
}
How exactly should I get data from this Promise object?
const retrieveData = async () => {
const keys = await localforage.keys()
// The return value of "keys.map" is an array of promises since
// async automatically returns a Promise behind the scenes.
// Await works on a single promise, not an array of promises,
// so "data" will not contain the actual data.
const data = await keys.map(async (key) => {
const item = await localforage.getItem(key)
return [item.username, item.compamy, item.email, item.lastUpdate]
})
return data
}
Do:
const retrieveData = async () => {
const keys = await localforage.keys()
const data = await Promise.all(keys.map(async (key) => {
const item = await localforage.getItem(key)
return [item.username, item.compamy, item.email, item.lastUpdate]
}));
return data
}
Or use Bluebird's map which works out of the box in this scenario:
// The "then" function does not do anything. It returns values,
// but only does so to the next "then" function. There are no
// further then-functions so the return value is unused.
// "values" is merely a local variable so you won't be able to
// access it anywhere outside the fat arrow function.
// You could move the console log into "then".
asyncData = retrieveData().then(values => values)
// asyncdata is still the unresolved promise object, the "then"
// function has not been run yet (then on the line above will be run when
// all of the awaits in retrieveData have been successfully resolved.
console.log(asyncData)
Do:
async componentDidMount() {
const data = await retrieveData();
console.log(data);
}
Or:
componentDidMount() {
retrieveData().then(values => {
console.log(values);
});
}
you can use for of loop here, and it will be simpler, we dont use await keyword directly in a map iterator, instead we can use promise.all as mentioned in above answer.
const retrieveData = async () => {
const keys = await localforage.keys();
let data;
for (let key of keys) {
const item = await localforage.getItem(key);
data.push([item.username, item.compamy, item.email, item.lastUpdate]);
}
return data;
}
Try using the "await" reserved keyword before your retrieveData() method at componentDidMount(), since it's a promise, an async event, you have to wait until it finishes all of it's inner executions to return some data and go on.
Just like you did at retrieveData() declaration use await before the promise. in detail what you need:
async componentDidMount() {
let asyncData = await retrieveData()
....
}

Async/await redux thunk not returning promise to action correctly

I have a thunk using Axios that's posting to an Express route using Sequelize.
The route is posting correctly (ie. data is getting added to the db) but the action inside of the React component isn't behaving as expected. Using async/await, I expect the action to wait until it completes the db post before continuing but that's not the case here. I'm getting undefined from the action.
The thunk hits the express route where I'm dispatching the action to update my redux store and returning the response:
const addedNewList = (newList) => ({type: ADD_NEW_LIST, newList})
export const addNewList = (name, userId) => async dispatch => {
try {
const { data } = await axios.post('/api/list/add', { name, userId })
dispatch(addedNewList(data))
return data
} catch (err) {
console.error(err)
}
}
Using debugger, I can confirm that return data is in fact returning the response from the server that I need. I can also confirm that the redux store is getting updated correctly.
But here, when I try and access that response data as result, I get undefined:
handleSubmit = async () => {
const result = await this.props.addNewList(this.state.name, this.props.userId)
// ** result is 'undefined' **
this.handleClose()
// pass off the results
}
If I add a setTimeout after I evoke the addNewList action, it works as expected. This suggests to me that maybe it's not returning a promise? But my understanding was that if you returned the response from the server in the thunk, it would do that.
For completeness, here is my route which I've also confirmed with debugger that data is being passed as expected:
const userAuth = function(req, res, next) {
if (req.isAuthenticated()) {
return next()
}
res.status(401).send('Unauthorized user')
}
router.post('/add', userAuth, async (req, res, next) => {
const { name, userId } = req.body
try {
const list = await List.create({ name, userId })
res.json(list)
} catch(err) { next(err) }
})
Why is the action returning undefined in the handleSubmit method?
Try returning the dispatch of addedNewList(data) instead:
export const addNewList = (name, userId) => async dispatch => {
try {
const { data } = await axios.post('/api/list/add', { name, userId })
return Promise.resolve(dispatch(addedNewList(data)));
} catch (err) {
console.error(err)
}
}
That being said, you could consider restructuring the component to instead utilize mapStateToProps to use values/result from the updated Redux store rather than explicitly awaiting the response and manually passing the value?
The response from Alexander got me on the right track so I'm sharing my solution in case it helps someone (as he suggested).
While I could have continued to try and solve this by wrapping the dispatch in a Promise, the better solution was to rethink how the component was structured.
In my situation, I wanted to get the ID for the newly created row in the database so that I could pass it into history.push.
handleSubmit = async () => {
const result = await this.props.addNewList(this.state.name, this.props.userId)
this.handleClose()
history.push(`/list/${result.id}`)
}
With result coming back undefined, the url was not updating correctly.
The better solution was to access the new data from the redux store where it was updated. This way I could be certain the history wouldn't get updated until the data was ready.
So my updated component now looked something like this where the history wouldn't update until a newId was available:
handleSubmit = () => {
this.props.addNewList(this.state.name, this.props.userId)
this.handleClose()
}
render(){
const { newId } = this.props
if (newId) {
history.push(`/list/${newId}`)
}
return (
....
)
}
}
const mapStateToProps = (state) => {
return {
newId: state.list.newId
}
}
Instead of putting this into render, I could probably also use a component lifecylcle method like componentWillReceiveProps or similar.

Resources