Javascript - Redux actions don't run consecutively - javascript

I have a situation when I need 2 Redux Actions to be run consecutively.
The context is a user clicks on a Preview button, and I want to display a loader until the puzzle is done generating.
function mapDispatchToProps(dispatch) {
return {
onPreview: () => {
dispatch(generatePreview());
},
};
}
In order to do it, I use the middleware redux-thunk and the action I want to be executed first returns a Promise.resolve() and my second action is in the then():
export function generatingPreview() {
return dispatch => {
dispatch({
type: GENERATING_PREVIEW,
});
return Promise.resolve();
};
}
export function generatePreview() {
return (dispatch, getState) => {
dispatch(generatingPreview()).then(() => {
const state = getState();
const conf = state.getIn(['login', 'conf']).toJS();
const wordList = state.getIn(['login', 'wordList']);
try {
const newPuzzle = Wordfind.newPuzzleLax(wordList, conf);
dispatch(generatePreviewSuccess(newPuzzle));
} catch (err) {
dispatch(generatePreviewError(err.message));
}
});
};
}
export function generatePreviewError(error) {
return {
type: GENERATE_PREVIEW_ERROR,
error,
};
}
export function generatePreviewSuccess(payload) {
return {
type: GENERATE_PREVIEW_SUCCESS,
payload,
};
}
Unfortunately, the loader never appears. I console.logged the state setting the loading to true when my component renders, and it changes! I can see the log but not the loader, the component doesn't really re-render until the actions generatePreviewSuccess() or generatePreviewError() are dispatched. And it's not an issue from the loader, if I replace the newPuzzleLax function by a loop in order to make enough time to see it, I can see it!
My theory is this function Wordfind.newPuzzleLax(wordList, conf) that I use to generate the puzzle is blocking the queue of actions because on the Chrome Redux Tools I an see the first action appearing at the same time that the second one:
Link to the function.
If I add a 1-microsecond delay between the dispatch of the two actions, the loader appears... but I would really like to understand what is happening. Thank you in advance. If it's any help, I use the react-boilerplate
I also tried to transform the function generating the puzzle as an async one by doing this:
const wordFindAsync = async (wordList, conf) =>
Wordfind.newPuzzleLax(wordList, conf);
export function generatePreview() {
return (dispatch, getState) => {
dispatch(generatingPreview())
.then(() => {
const state = getState();
const conf = state.getIn(['login', 'conf']).toJS();
const wordList = state.getIn(['login', 'wordList']);
wordFindAsync(wordList, conf);
})
.then(res => dispatch(generatePreviewSuccess(res)))
.catch(err => {
dispatch(generatePreviewError(err.message));
});
};
}

In your second version you're not returning the Promise from wordFindAsync(wordList, conf) back into your original Promise chain, and so its not being resolved/waited on by then next then.
export function generatePreview() {
return (dispatch, getState) => {
dispatch(generatingPreview())
.then(() => {
const state = getState();
const conf = state.getIn(['login', 'conf']).toJS();
const wordList = state.getIn(['login', 'wordList']);
return wordFindAsync(wordList, conf); // 🌟 return your promise here
})
.then(res => dispatch(generatePreviewSuccess(res)))
.catch(err => {
dispatch(generatePreviewError(err.message));
});
};
}
Here's a simple example demoing the behavior I'm refering to.
This one will only wait 1 second until logging "done":
const waitOneSec = () =>
new Promise(resolve => {
console.log("waiting 1 secoond");
setTimeout(resolve, 1000);
});
waitOneSec()
.then(() => {
waitOneSec(); // Promise not returned
})
.then(() => console.log("done"));
Whereas this one will wait full 2 seconds until logging "done":
const waitOneSec = () =>
new Promise(resolve => {
console.log("waiting 1 secoond");
setTimeout(resolve, 1000);
});
waitOneSec()
.then(() => {
return waitOneSec(); // 🌟 Promise returned
})
.then(() => console.log("done"));
Hope that helps.

Related

How can I dispatch async data with Vue 3 store

I am pulling data from an api as async and transferring the payload value returned from this api to the state field in the store with store.dispatch.
But at first this state is empty. When I make a change on the page and render it, the state is filled.
the function I pulled the api
const getTransferredOrder = async () => {
isLoading.value = true;
return await TransferredOrderService.getTransferredOrderSummary()
.then((payload) => {
store.dispatch('GetTransferredList',payload)
return payload;
})
.catch(() => {
return [];
}).finally(() => {
isLoading.value = false
});
}
i call this function first in onmounted
onMounted(async () => {
await getTransferredOrder()
})
my actions,mutations and state js files
actions.js
const actions={
GetTransferredList({commit},payload){
commit('GET_TRANSFERRED_ORDER_LIST',payload)
},
}
export default actions
mutations.js
const mutations={
GET_TRANSFERRED_ORDER_LIST(state,payload){
state.transferredOrderList = payload;
},
}
export default mutations
state.js here
const state={
transferredOrderList:[],
}
export default state
Since the state is empty in the place where I store.dispatch, it is also empty on other pages.
But when I write a small console.log on the page and compile it, the store.state fills the page. What exactly does that have to do with it?
Can you look at console and write errors here.
Why are you returns? Can you change code to ->
const getTransferredOrder = async () => {
isLoading.value = true;
await TransferredOrderService.getTransferredOrderSummary()
.then((payload) => {
store.dispatch('GetTransferredList',payload);
})
.catch(() => {
store.dispatch('GetTransferredList',[]);
}).finally(() => {
isLoading.value = false
});
}

Redux - Asynchronous response from web socket request

I have a websocket interface which I implemented so that I can use to send requests.
The problem is that the response is asynchronous and it initially returns the empty array because retObj is not updated from the callback function that I sent in. How can I make this function so that it will return the populated array when it has been updated.
This is how my Service looks like:
import * as interface from '../webSocket'
const carService = () => {
return {
getCars: () => {
interface.sendRequest(function (returnObject) {
//
}).then(d => d)
}
}
}
export default carService()
And this is how my action looks like:
import { GET_CARS } from '../constants'
import carService from '../carService'
export const getCars = () => async (dispatch) => {
try {
const cars = await carService.getCars()
console.log("At cars actions: ", cars) // logs: Array []
dispatch(getCarsSuccess(cars))
} catch (err) {
console.log('Error: ', err)
}
}
const getCarsSuccess = (cars) => ({
type: GET_CARS,
payload: cars
})
You simply have to wrap your callback into promise, since it was not a promise to begin with, which is why you cannot use then or await
import * as interface from '../webSocket'
const carService = () => {
return {
getCars: () => {
return new Promise(resolve => interface.sendRequest(function (returnObject) {
resolve(returnObject.msg)
}));
}
}
}
export default carService()
The problem is, you cant await a function unless it returns a Promise. So, as you can guess, the problem lies in carService.getCars's definition. Try this:
getCars: () => {
return new Promise((resolve, reject) => {
interface.sendRequest(function(returnObject) {
// if theres an error, reject(error)
resolve(returnObject);
})
})
}
Or, if sendRequest os am async function, simply return the return value of sendRequest:
getCars: () => {
return interface.sendRequest()
}

How to wait for a successful async action before changing url?

so I'm using a popup to log my users in with firebase:
const loginToApp = (provider) => {
firebaseApp
.auth()
.signInWithPopup(provider)
.then(async (result) => {
if (result.additionalUserInfo.isNewUser) {
// problem is this line
await setNewUserInformation(result.user.uid)
}
const { user } = result
setUser(user)
// and this line
window.location.href = 'newRoute'
})
.catch((error) => {
console.log('ERROR:', error)
})
}
so if I remove window.location.href = 'visited' this all works fine and it sets in firebase. I'm probably doing something stupid but I cant figure out how to wait for this function to fire setNewUserInformation and to complete before I move to the new page?
function code:
export const setNewUserInformation = (userId) => {
return {
type: 'SET_NEW_USER_INFORMATION',
userId,
}
}
this then has a redux observable epic listening to it:
return action$.pipe(
ofType('SET_NEW_USER_INFORMATION'),
mergeMap((action) => {
return from(
firebaseApp.database().ref(firebaseRef).update(userInformation),
).pipe(
mergeMap(() => {
return [updatedUserInformationSuccess()]
}),
catchError((error) => of(updatedUserInformationFailure(error))),
)
}),
)
setNewUserInformation() is an action creator, which is sync. You do not need to wait for it as it does not return anything useful to you logic. What you need to do, is move window.location.href = 'newRoute' to separate logic, and make it depend on state returned from action creators updatedUserInformationSuccess() and updatedUserInformationFailure(error). If your component is functional, put this logic in a useEffect. If it is a class component, use ComponentDidUpdate lifecycle method.
Use it like below
const loginToApp = (provider) => {
firebaseApp
.auth()
.signInWithPopup(provider)
.then(async (result) => {
new Promise((resolve, reject) => {
if (result.additionalUserInfo.isNewUser) {
// problem is this line
setNewUserInformation(result.user.uid)
}
const { user } = result
resolve(user)
}).then((user)=>{
setUser(user)
// and this line
window.location.href = 'newRoute'
})
})
.catch((error) => {
console.log('ERROR:', error)
})
}
Because on then You can returned a Promise and resolve later. We could re-write the code above like this below:
const loginToApp = (provider) => {
firebaseApp
.auth()
.signInWithPopup(provider)
.then((result) => {
if (result.additionalUserInfo.isNewUser) {
// return for next resolve function
return setNewUserInformation(result.user.uid).then(() => result);
}
return result;
})
.then((result) => {
// after all above promises resolve
const { user } = result
setUser(user)
// and this line
window.location.href = 'newRoute'
})
.catch((error) => {
console.log('ERROR:', error)
})
}
Are you using React?
If yes, then you can simply use didUpdate Cycle to route to new url after successful action dispatched. Move your "window.location.href = 'newRoute'" under the ComponentDidUpdate with props check.

Testing Redux Thunk Action Creator

I've got a redux action creator that utilizes redux-thunk to do some logic to determine what to dispatch to the store. Its not promise-based, like an HTTP request would be, so I am having some issues with how to test it properly. Ill need a test for when the value meets the condition and for when it doesn't. Since the action creator does not return a promise, I cannot run a .then() in my test. What is the best way to test something like this?
Likewise, I believe it would be pretty straightforward testing the getRemoveFileMetrics() action creator as it actually does return a promise. But how can I assert that that will called if the value is removeFiles and meets the condition? How can that be written in the test?
Thanks in advance as this has had me stuck for the last couple of days.
Action Creators
export const handleSelection = (value, cacheKey) => {
return dispatch => {
if (value === "removeFiles") {
dispatch(getRemoveFileMetrics(cacheKey));
}
dispatch({ type: HANDLE_SELECTION, value });
};
};
export const getRemoveFileMetrics = cacheKey => {
return dispatch => {
dispatch({ type: IS_FETCHING_DELETE_METRICS });
return axios
.get(`../GetRemoveFileMetrics`, { params: { cacheKey } })
.then(response => {
dispatch({ type: GET_REMOVE_FILE_METRICS, payload: response.data });
})
.catch(err => console.log(err));
};
};
Jest
it("should dispatch HANDLE_SELECTION when selecting operation", () => {
const store = mockStore({});
const value = "switchVersion";
const expectedAction = [{
type: MOA.HANDLE_SELECTION,
value,
}]; // TypeError: Cannot read property 'then' of undefined
return store.dispatch(MOA.handleSelection(value)).then(() => {
const returnedActions = store.getActions();
expect(returnedActions).toEqual(expectedAction);
});
});
NEW EDIT
So based off of Danny Delott's answer to return a promise, I acheived a passing test as follows:
export const handleSelection = (value, cacheKey) => {
return dispatch => {
if (value === "removeFiles") {
return dispatch(getRemoveFileMetrics(cacheKey));
}
return new Promise((resolve, reject) => {
resolve(dispatch({ type: HANDLE_SELECTION, value }));
});
};
};
Is there a reason to explicitly NOT return a promise in your action creator? It looks like getRemoveFileMetrics is returning the promise, it just gets swallowed in handleSelection...
Easiest solution is to just return the promise:
export const handleSelection = (value, cacheKey) => {
return dispatch => {
if (value === "removeFiles") {
return dispatch(getRemoveFileMetrics(cacheKey));
}
dispatch({ type: HANDLE_SELECTION, value });
return new Promise();
};
};
Otherwise, you'll need make your assertions after the event loop is finished. You can do with a setTimeout wrapped in a Promise to get the .then behavior.
it("should dispatch HANDLE_SELECTION when selecting operation", () => {
const store = mockStore({});
const value = "switchVersion";
const expectedAction = [{
type: MOA.HANDLE_SELECTION,
value,
}];
store.dispatch(MOA.handleSelection(value));
// flush outstanding async tasks
return new Promise(resolve => {
setTimeout(resolve, 0);
})
.then(() => {
const returnedActions = store.getActions();
expect(returnedActions).toEqual(expectedAction);
});
});

Dispatching an action in a debounced function using redux-thunk

I have the following debounced function that gets called every time a user inputs into the username field. It is working as expected.
export const uniqueUsernameCheck = _.debounce(({ username }) => {
axios.post(`${API_URL}/signup/usernamecheck`, { username })
.then((res) => {
console.log('Is unique?', res.data.status);
})
.catch((error) => {
console.log(error);
});
}, 500);
However using redux-thunk I am trying to modify the function so that I can dispatch actions within my function. This is what I have:
export const uniqueUsernameCheck = _.debounce(({ username }) => {
console.log('I can see this');
return (dispatch) => {
console.log('But not this');
dispatch({ type: USERNAME_CHECK });
axios.post(`${API_URL}/signup/usernamecheck`, { username })
.then((res) => {
dispatch(authError(res.data.error));
})
.catch((error) => {
console.log(error);
});
};
}, 500);
The problem lies in that the above code no longer fires off my post request like the initial function did and nothing ever gets dispatched. I know I'm doing something wrong but can't figure out what.
EDIT:
This is how I've set up my store
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
Take a look at this:
http://codepen.io/anon/pen/egeOyJ
const userService = _.debounce(username => {
setTimeout(
()=>{
console.log('userService called after debounce. username:', username)
}
,1000)
}, 500)
const uniqueUsernameCheck = (username) => (dispatch) => {
console.log('I can see this')
userService(username)
}
console.log('begin')
const reducers = (action) => {console.log(action)}
const store = Redux.createStore(
reducers,
{},
Redux.applyMiddleware(ReduxThunk.default))
store.dispatch(uniqueUsernameCheck('rafael'))
store.dispatch(uniqueUsernameCheck('rafael'))
store.dispatch(uniqueUsernameCheck('rafael'))

Categories

Resources