can't I dispatch actions in sequence? - reactjs

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.

Related

React Redux - Wait until data is loaded

I'm using Redux (+ thunk) to fetch data from my API. I implemented a Data Fetcher component that calls all the actions and once done, dispatches a LOADED. In my actual main component where I render content, I wait until isLoaded flag in the props is set to true.
Here's the method in the data fetcher:
const fetchData = () => {
if (isAuthenticated) {
const p = [];
p.push(fetchDataSetOne());
p.push(fetchDataSetTwo());
Promise.all(p).then( () => setHasLoaded() );
}
}
Each of those fetch methods returns an axios promise, in which I dispatch once retrieved like so:
export const fetchDataSetOne = () => dispatch => {
return axios.get(`${API_URL}/dataSetOne`)
.then(res => {
dispatch({
type: FETCH_ALL_DATA_SET_ONE,
payload: res.data.docs
});
});
};
In my component's render function I only render the content when loaded (which is set by the LOADED dispatch from the setHasLoaded action) like so:
{ hasLoaded && <MyContent> }
Even though I "wait" for the actions to finish (= Promise.all), my variable hasLoaded is set to true before the fetched data is set. Can anybody help?
The problem is you return a function NOT a promise.
This resolves immediately.
See working code sample
export const fetchData2 = dispatch => () => {
dispatch({type: 'START_FETCH'})
const p = [
fetchDataSetOne(dispatch),
fetchDataSetTwo(dispatch)
];
Promise.all(p).then((res) => setHasLoaded(res));
};
// this returns a promise AFTER it calls an action
const fetchDataSetOne = dispatch => {
return axois.get(`${API_URL}/dataSetOne`).then(res => {
dispatch({
type: "FETCH_ALL_DATA_SET_ONE",
payload: res.data.docs
});
});
};
This resolves after both promises are resolved, but the state updates after each promise is resolved. To update state after all promises resolve, try this:
export const fetchData3 = dispatch => () => {
dispatch({ type: "START_FETCH" });
const p = [
axois.get(`${API_URL}/dataSetOne`),
axois.get(`${API_URL}/dataSetTwo`)
];
Promise.all(p).then(callActions(dispatch));
};
const callActions = dispatch => res => {
dispatch({
type: "FETCH_ALL_DATA_SET_ONE",
payload: res[0].data.docs
});
dispatch({
type: "FETCH_ALL_DATA_SET_TWO",
payload: res[1].data.docs
});
setHasLoaded(res);
};

How to wait 'return' until 'dispatch' finish

[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.

How to know if a function is called inside redux thunk dispatch?

I'm writing a function to send API requests. When I get the response from that API I want to dispatch redux action if the user has called my function in dispatch or just do nothing if not. I'm using redux thunk.
Right now I've written two separate methods for this.
This does not dispatch after getting the response from the API, just return the Promise.
const getAnimalsList = () => return axios.request({url: 'api.domain.com/animals'});
This function will be called as usual functions.
It dispatches an action something after getting a response from the API
const getAnimalsList = () => (dispatch, getState) => {
axios.request({url: 'api.domain.com/animals'}).then(
res => dispatch({type: 'RESPONSE RECEIVED', data: res}),
err => dispatch({type: 'ERROR', err})
);
}
This function will be called inside dispatch as dispatch(getAnimalsList())
Now what I want is to know in a single function whether it was called inside the dispatch or just called normally.
example:
const getAnimalsLis = () => {
let prom = axios.reques({url: 'api.domain.com/animals});
if(function_is_called_inside_dispatch){
return dispatch => {
prom.then(
res => dispatch({type: 'RESPONSE RECEIVED', data: res}),
err => dispatch({type: 'ERROR', err})
);
}
}
else return prom;
}
This is a wrong way to do things. There's no way to detect that the function was called like dispatch(getAnimalsList()) or just getAnimalsList(). Due to operator precedence, it has already been called as getAnimalsList() when an action provided to dispatch. The only way would be to call it differently, dispatch(getAnimalsList(CALLED_AS_AN_ACTION)).
A correct way is to not mix functions that serve different purposes. The code can be made DRYer than it is, getAnimalsList function already contains common code that could be extracted otherwise:
const getAnimalsList = () => return axios.request({url: 'api.domain.com/animals'});
const getAnimalsListAction = () => (dispatch, getState) => {
return getAnimalsList().then(
res => dispatch({type: 'RESPONSE RECEIVED', data: res}),
err => dispatch({type: 'ERROR', err})
);
}
Please do not define getState if you are not planning on using it:
const getAnimalsList = () => (dispatch, getState) => {
axios.request({url: 'api.domain.com/animals'}).then(
res => dispatch({type: 'RESPONSE RECEIVED', data: res}),
err => dispatch({type: 'ERROR', err})
);
}
And also use the async/await syntax, its more explicit for you as to what is going on:
export const getAnimalsList = () => async dispatch => {
const response = await jsonAnimals.get("/animals");
dispatch({ type: "RESPONSE_RECEIVED", payload: response.data });
};
Then create a folder/file system like so: apis/jsonAnimals.js:
Place your Axios code in there:
import axios from 'axios';
export default axios.create({
baseURL: 'http://api.domain.com'
});
Okay, now you have a nice clean Redux application, makes it easier on the eyes, easier to debug.
Now if you want to test it in the console then you could do
export const testGetAnimalsList = () => async (dispatch, getState) => {
await dispatch(getAnimalsList());
console.log(getState().animals);
};
export const getAnimalsList = () => async dispatch => {
const response = await jsonAnimals.get("/animals");
dispatch({ type: "RESPONSE_RECEIVED", payload: response.data });
};

How to call sequential action one after another -- and make sure both get rendered (react-redux)?

I want to add an item using a POST request, then close the pop-up window and update the item list with a GET request. Using redux-thunk, I could call the 2 latter actions after the getting result from POST.
Now I want to call loadAll action to populate the updated list of item and finish the state change/rendering before calling toggleAddItem, which actually close the pop-up window.
export const loadAllItems = items => ({
type: LOAD_ALL_ITEMS,
payload: items
});
export const toggleAddItem = status => ({
type: TOGGLE_ADD_ITEM,
payload: status
})
export const loadAll = () => (dispatch => {
axios.get('/getAllItems').then(res => {
dispatch(loadAllItems(res.data))
})
.catch(err => {
console.log(err);
})
});
export const addItemAndRefresh = input => ((dispatch, getState) => {
axios.post('/addItem', input)
.then(() => {
axios.get('/getAllItems')
.then(res => {
dispatch(loadAll(res.data))
})
.then(() => {
dispatch(toggleAddItem(false));
})
})
.catch(err => {
console.log(err);
})
});
With the above code, I manage to call the loadAll before toggleAddItem. However, with componentDidUpdate log, I realize that toggleAddItem actually fires when loadAll is still updating state/rendering and even finish before the former action.
So far, this is my best attempt, however, I'm not sure if this sequence can avoid all issues. There are previous cases when pop-up window is closed, and state is updated with new items. However, new item list is not rendered (I suspect that this happens because I update the state while the rendering function is working, so it skips the second render).
What would be the best implementation for dispatching consequential actions and make sure that both will be rendered?
You need to call then on the promise returned from the loadAll action.
Firstly return that promise by changing your loadAll to:
export const loadAll = () => dispatch => {
return axios.get('/getAllItems')
.then(res => {
dispatch(loadAllItems(res.data))
})
.catch(err => {
console.log(err);
})
};
Then in your mapDispatchToProps call then and then pass a function which will be called when loadAll resolves.
const mapDispatchToProps = dispatch => ({
addItemAndRefresh: dispatch(loadAll())
.then(() => dispatch(toggleAddItem(false)))
});

React Redux with Redux Observable wait for authentication action to finish before continuing

My background is .NET development, WPF precisely. When you wanted to call an authenticated action in C# this is what you roughly have to do.
var res = action();
if(res == Not authenticated)
{
var cred = await showDialog();
action(cred);
}
Pretty simple, call an action, if you are not authenticated show dialog window where user can enter credentials and call action with credentials again.
Now I'm trying to implement the same functionality in redux with epics.
export const pullCurrentBranchEpic = (action$: ActionsObservable<Action>, store: Store<ApplicationState>) =>
action$
.filter(actions.pullCurrentBranch.started.match)
.mergeMap(action => Observable.merge(
Observable.of(repoActions.authenticate.started({})),
waitForActions(action$, repoActions.authenticate.done)
.mergeMap(async _=>{
const repository = await getCurrentRepository(store.getState());
await pullCurrentBranch(repository);
})
.map(_=>actions.pullCurrentBranch.done({params:{},result:{}}))
.race(
action$.ofType(repoActions.authenticate.failed.type)
.map(() => actions.pullCurrentBranch.failed({error:"User not authenticated",params:{}}))
.take(1))
));
export const waitForActions = (action$, ...reduxActions) => {
const actionTypes = reduxActions.map(x => x.type);
const obs = actionTypes.map(type => action$.ofType(type).take(1));
return Observable.forkJoin(obs);
};
Basically epic will handle an action (I don't even check if user should be authenticated or not because it will double this code) and action will run another action - authenticate.started which will change a state in a reducer to open the window, user will be able to enter his credentials and when he does it, it will call authenticate.done action. When it's done, epic will continue with a promise to call an action which will raise another action when it's finished. Am I doing something wrong? Isn't it just too complicated for such a simple thing? I don't think it's an issue with redux but rather redux observable and epics.
This might fit your needs. The important thing to note is breaking down the problem into multiple epics.
const startAuthEpic = action$ => action$
.filter(action.pullCurrentBranch.start.match)
.map(action => repoActions.authenticate.started({});
const authDoneEpic = (action$, store) => action$
.ofType(repoActions.authenticate.done.type)
.mergeMap(() => {
const repo = getCurrentRepository(store.getState());
return Observable.fromPromise(pullCurrentBranch(repo))
.map(() => actions.pullCurrentBranch.done({ ... }))
.catch(error => Observable.of(actions.pullCurrentBranch.failed({ ... })))
});
There are multiple ways to include more authenticated actions, one being:
// each new authenticated action would need to fulfill the below contract
const authenticatedActions = [
{
action: 'pull',
match: action.pullCurrentBranch.start.match,
call: store => {
const repo = getCurrentRepository(store.getState());
return pullCurrentBranch(repo);
},
onOk: () => actions.pullCurrentBranch.done({}),
onError: () => actions.pullCurrentBranch.failed({})
},
{
action: 'push',
match: action.pushCurrentBranch.start.match,
call: store => {
const repo = getCurrentRepository(store.getState());
return pushCurrentBranch(repo);
},
onOk: () => actions.pushCurrentBranch.done({}),
onError: () => actions.pushCurrentBranch.failed({})
}
];
const startAuthEpic = action$ => action$
.map(action => ({ action, matched: authenticatedActions.find(a => a.match(action)) }))
.filter(wrapped => wrapped.matched)
.map(wrapped => repoActions.authenticate.started({ whenAuthenticated: wrapped.matched }));
const authDoneEpic = (action$, store) => action$
.ofType(repoActions.authenticated.done.type)
.pluck('whenAuthenticated')
.mergeMap(action => {
const { call, onOk, onError } = action;
return Observable.fromPromise(call(store))
.map(onOk)
.catch(err => Observable.of(onError(err)))
});
You would just have to pass along the whenAuthenticated property to the authenticate.done action.

Resources