How can I made fetch chain async actions where second fetch is using data from first? I need fetch repository list (GitHub API) and then fetch users from those repos. I made this:
export function reposListFetchData(url) {
return (dispatch) => {
dispatch(reposListIsLoading(true))
fetch(url)
.then((response) => {
if(!response.ok){
throw Error(response.statusText)
}
dispatch(reposListIsLoading(false))
return response
})
.then((response) => response.json())
.then((repos) => dispatch(reposListFetchSuccess(repos)))
.then( this.props.repos.map(
repo=>this.props.fetchContributorsData(`https://api.github.com/repos/angular/${repo.name}/contributors?per_page=100`)
))
.catch(()=> dispatch(reposListHasErrored(true)))
}
}
but of course I cant use this.props there. Any suggestions?
Assuming fetchContributorsData is an action that is quite similar with the reposListFetchData, you should be able to do this...
export function reposListFetchData(url) {
return dispatch => {
dispatch(reposListIsLoading(true));
fetch(url)
.then(response => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(reposListIsLoading(false));
return response;
})
.then(response => response.json())
.then(repos => {
dispatch(reposListFetchSuccess(repos));
// where repos is an array of repo
repos.map(repo =>
dispatch(fetchContributorsData(`https://api.github.com/repos/angular/${repo.name}/contributors?per_page=100`))
);
})
.catch(() => dispatch(reposListHasErrored(true)));
};
}
Related
I have this action to fetch the details of a specific location url stored in Firebase.
The original code (Version 1) worked no problem, whereby I dispatch authGetToken(), the code recognises the token (string) stored in redux, then uses it to to fetch the stored data.
Version 1
return dispatch => {
dispatch(authGetToken())
.then(token => {
return fetch("https://myProject/location.json?auth=" + token);
})
.catch(() => {
alert("No valid token found!");
})
.then(res => {
if (res.ok) {
return res.json();
} else {
throw(new Error());
}
})
};
But now that I modified the url requirements to include the user UID as part of the url, it does not work. I know there must be a flaw in my logic but I can't see it.
What I was hoping to write is that once I dispatch authGetToken(), the token dispatches authGetUserUID then uses both strings (userUID and token) to fetch the data.
Version 2
return dispatch => {
dispatch(authGetToken())
.then(token => {
dispatch(authGetuserUID())
return fetch("https://myProject/location/"+ userUID + ".json?auth=" + token);
})
.catch(() => {
alert("No valid token found!");
})
.then(res => {
if (res.ok) {
return res.json();
} else {
throw(new Error());
}
})
};
Would appreciate you guys pointing out the obvious to me >< as I my noob eyes can't see it. Thanks in advance.
I think it might have something to do with userUID, it doesn't seem to be initialised anywhere. Maybe try something like this:
return dispatch => {
dispatch(authGetToken()).then(token => {
dispatch(authGetuserUID()).then(userUID=>{
return fetch("https://myProject/location/"+ userUID + ".json?auth=" + token);
})
})
.catch(() => {
alert("No valid token found!");
})
.then(res => {
if (res.ok) {
return res.json();
} else {
throw(new Error());
}
})
};
I changed the signature of an action creator to make a call to getState before trying to dispatch fetch, but now fetch is not getting called.
StartingPoint: I have an async action that makes an api call using fetch and then dispatches a success or error action once it's done, as below. I call this action from a container like so and it works fine:
dispatch(actions.getData()); //from a container
export function getData(){
return (dispatch : any) => {
return fetch(
'http://localhost:8000/api',{}
).then(
response => response.json()
).then(
json => dispatch(successAction(json)),
err => dispatch(notify("SERVER_ERROR"))
);
}
}
The problem is that I need to call getState in the action, so that I can have an option about which port to query. Therefore, I changed the getData action to what you see below. However, when I call the action creator like this dispatch(actions.getData());, it's not making a network call, although the console.log statement is running.
Question: how can the getData function be written to allow for making a call to getState before running the fetch? (and, related, what is the purpose of wrapping it in the dispatch return)?
export const getData = () => (dispatch: any, getState: any) => {
let state = getState();
let url = //code omitted - getting port from state object
console.log("this log statement runs");
return (dispatch : any) => {
return fetch(
url,{}
).then(
response => response.json()
).then(
json => dispatch(successAction(json)),
err => dispatch(notify("SERVER_ERROR"))
);
}
}
added Promise support
const addPromiseSupportToDispatch = (store: any) => {
const rawDispatch = store.dispatch;
return (action: any) => {
if (typeof action.then === 'function') {
return action.then(rawDispatch);
}
return rawDispatch(action);
};
};
store.dispatch = addPromiseSupportToDispatch(store);
You inserted an additional return I think. This should be the right code block
export const getData = () => (dispatch: any, getState: any) => {
let state = getState();
let url = //code omitted - getting port from state object
console.log("this log statement runs");
return fetch(
url,{}
).then(
response => response.json()
).then(
json => dispatch(successAction(json)),
err => dispatch(notify("SERVER_ERROR"))
);
}
EDIT
If I had to use your original code:
export function getData(){
return (dispatch : any, getState: any) => { // <= second parameter provided by redux-thunk
let url = getState().url; //can call getState here
return fetch(
'http://localhost:8000/api',{}
).then(
response => response.json()
).then(
json => dispatch(successAction(json)),
err => dispatch(notify("SERVER_ERROR"))
);
}
}
Since nock is compatible with axios, and moxios doesn't have much documentation, I decided to use fetch. However now my actions aren't being dispatched.
import { thunk } from 'redux-thunk';
import axios from 'axios';
import {
CURRENT_CONDITION_SUCCESS,
CURRENT_CONDITION_ERROR,
LOAD,
} from './types';
export function initializeLoad() {
return (dispatch) => {
dispatch({type: LOAD, bool: true})
fetch("http://api.wunderground.com/api/777ba6b403bf06b7/geolookup/q/autoip.json")
.then(res => dispatch(getCurrentConditions(res.data.location.l)))
.catch(err => dispatch({type: CURRENT_CONDITION_ERROR, error: err}));
}
}
export function getCurrentConditions(location) {
return (dispatch, getState) => {
fetch(`http://api.wunderground.com/api/777ba6b403bf06b7/conditions/${location}.json`)
.then(condition => dispatch({type: CURRENT_CONDITION_SUCCESS, condition: condition.data.current_observation, input: ''}))
.then(() => {
if(getState.isLoading) {
setTimeout(() => {
dispatch({type: LOAD, bool: false})
}, 2000)
}
})
.catch(err => dispatch({type: CURRENT_CONDITION_ERROR, error: err.data}));
}
}
Any idea why?
Checking the network tab, I know I'm getting a response of 200 with all the data I'm expecting, but dispatch(getCurrentConditions) isn't getting called.
However, if I replace fetch with axios.get, everything works.
I am trying to get route params and then get data from the service
this.route.params
.switchMap(params => this.service.getData(params['id']))
.subscribe(value => this.value = value,
error => console.log(error));
This works fine, until first error. After the error this line doesn't calls no more params => this.noteService.GetParams(params['id']).
I can write something like this, but i think there is a better way
this.route.params.subscribe(
params => {
this.service.getData(params['id']).subscribe(
result => console.log(result),
error => console.log(error))
});
My service
public getData(id): Observable<any> {
return this.http.get('api/data/' + id)
.map(data => data.json())
.catch(error => Observable.throw(error));
}
Update
This answer helped me a lot to understand what is going on.
When I call Observable.throw(error) subscription to route params stops with an error. So instead of throwing error I just need to return empty observable.
my.component.ts
this.route.params
.switchMap(params => this.service.GetData(params['id']))
.subscribe(result => {
if (result) this.data = result;
else console.log('error');
});
my.service.ts
public GetData(id): Observable<any> {
let url = 'api/data' + id;
return this.http.get(url)
.map(data => data.json())
.catch(error => Observable.of(null));
}
I'm building a github users application right now and had the same problem.
Here is a solution that works for me:
users.service.ts
public getByUsername(username: string): Observable<any[]> {
return this.http
.get(`${this.url}/${username}`)
.map((res: Response) => res.json());
}
user.component.ts
ngOnInit() {
this.sub = this.route.params
.flatMap((v: any, index: number) => {
return this.usersService.getByUsername(v.name);
})
.subscribe(data => this.user = data);
}
So, basically the flatMap operator does the trick.
Here is link to another question,
helping me to figure out how things work with chaining RxJS Observables
I'm very bad when it comes to thinking of a title question, sorry for that.
My Problem:
I'm unit testing my async redux actions like it's suggested in the docs. I mock the API calls with nock and check for the dispatched actions with redux-mock-store. It works great so far, but I have one test that fails even though it clearly does work. The dispatched action neither does show up in the array returned by store.getActions() nor is the state changed in store.getState(). I'm sure that it does happen because I can see it when I test manually and observe it with Redux Dev Tools.
The only thing that is different in this action dispatch is that it is called in a promise in a catch of another promise. (I know that sounds confusing, just look at the code!)
What my code looks like:
The action:
export const login = (email, password) => {
return dispatch => {
dispatch(requestSession());
return httpPost(sessionUrl, {
session: {
email,
password
}
})
.then(data => {
dispatch(setUser(data.user));
dispatch(push('/admin'));
})
.catch(error => {
error.response.json()
.then(data => {
dispatch(setError(data.error))
})
});
};
}
This httpPost method is just a wrapper around fetch that throws if the status code is not in the 200-299 range and already parses the json to an object if it doesn't fail. I can add it here if it seems relevant, but I don't want to make this longer then it already is.
The action that doesn't show up is dispatch(setError(data.error)).
The test:
it('should create a SET_SESSION_ERROR action', () => {
nock(/example\.com/)
.post(sessionPath, {
session: {
email: fakeUser.email,
password: ''
}
})
.reply(422, {
error: "Invalid email or password"
})
const store = mockStore({
session: {
isFetching: false,
user: null,
error: null
}
});
return store.dispatch(actions.login(
fakeUser.email,
""))
.then(() => {
expect(store.getActions()).toInclude({
type: 'SET_SESSION_ERROR',
error: 'Invalid email or password'
})
})
});
Thanks for even reading.
Edit:
The setErroraction:
const setError = (error) => ({
type: 'SET_SESSION_ERROR',
error,
});
The httpPostmethod:
export const httpPost = (url, data) => (
fetch(url, {
method: 'POST',
headers: createHeaders(),
body: JSON.stringify(data),
})
.then(checkStatus)
.then(response => response.json())
);
const checkStatus = (response) => {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
};
Because of you are using nested async function in catch method - you need to return the promise:
.catch(error => {
return error.response.json()
.then(data => {
dispatch(setError(data.error))
})
});
Otherwise, dispatch will be called after your assertion.
See primitive examples:
https://jsfiddle.net/d5fynntw/ - Without returning
https://jsfiddle.net/9b1z73xs/ - With returning