Is there a better way to handle api calls in javascript/react - javascript

I've build a function to make an api call to a weather service and then update state based on if it resolves, or rejects. The code works perfectly, but it feels messy to me.
getForecastedWeather(){
const endpoint = `http://dataservice.accuweather.com/forecasts/v1/daily/5day/207931?apikey=mG0ISFW1ZGGHIV3rs5CSFQlF92CYSqhr&language=en&details=true&metric=true`;
const fetchPromise = fetch(endpoint);
fetchPromise
.then(response => response.json())
.then(result => {
this.setState(state => ({
forecasts: {
...state.forecasts,
error: null,
isLoaded:true,
forecasts:result.DailyForecasts
},
}));
}).catch(error => {
this.setState(state => ({
forecasts: {
...state.forecasts,
error,
isLoaded:false
},
}));
})
}
Is there a better way of handling this?

Please consider using async await syntax:
async getForecastedWeather(){
try {
const endpoint = `http://...`;
const response = await fetch(endpoint);
const result = await response.json();
this.setState(state => ({ ... }));
} catch (error) {
this.setState(state => ({ ... }));
}
}

Related

Is there a way to make an api call within a map of another api call?

I know the title is quite confusing, I wasn't sure how to word it better. What I am trying to do is to fetch some items, map through those items to display them, but the problem is that one of those items has a value of what needs to be another api call to access it.
This is what I'm trying to do:
First of all I am storing an empty state, which later on becomes the data of the fetched items:
const [data, setData] = useState([]);
I'm using axios to fetch and store the data:
const fetchItems = () => {
axios("https://swapi.dev/api/people")
.then((response) => {
console.log(response.data.results);
const newData = response.data.results.map((item) => ({
name: item.name,
homeworld: () => {
axios.get(item.homeworld).then((response) => {
response.data.results;
});
},
}));
setData(newData);
})
.catch((error) => {
console.log("error", error);
});
};
It works with the name because it's a simple value. However, the homeworld includes a link that needs to be called once again in order to access it, instead of being a simple value like the name in this case. How can I call it and access what values are held within that link, and display them instead of just displaying the url?
I hope this can help you:
const [data,setData] = useState([])
const fetchItems = () => {
axios("https://swapi.dev/api/people")
.then(response => {
console.log(response.data.results);
const { results } = response.data;
for (const item of results) {
axios.get(item.homeworld).then(({data}) => {
setData([...data,{ name: item.name, homeworld: data.results }]);
});
}
})
.catch(error => {
console.log("error", error);
});
};
or with fetch:
const [data,setData] = useState([])
fetch("https://swapi.dev/api/people").then(re=>re.json())
.then(response => {
const newData = []
const { results } = response;
const newData = [];
for (const item of results) {
fetch(item.homeworld).then(re => re.json()).then((data) => {
newData.push({ name: item.name, homeworld: data });
});
}
console.log(newData)
setData(newData)
})
.catch(error => {
console.log("error", error);
});
Use Promise.all()
You can use Promise.all() method to get all the information you need by creating an array of promises by mapping the response.results array with an async function.
This is the code example
const fetchItems = async () => {
const req = await axios.get("https://swapi.dev/api/people");
const response = await req.data;
const allDataPromises = response.results.map(async (item) => {
const itemReq = await axios.get(item.homeworld);
const itemResponse = await itemReq.data;
return {
name: item.name,
homeworld: itemResponse,
};
});
const allData = await Promise.all(allDataPromises);
};
For further information about Promise.all()

How to mock multiple promise .then in react hooks and check for state set in one .then

The question could be seen as similar to this one but is not working really the same way as that one is checking for a function to be called while im looking for a state to change.
The code i have is this one (headers and body are not really important in this case):
const useGetToken = () => {
const [token, setToken] = useState();
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const fetchToken = useCallback(() => {
setLoading(true);
fetch('http://localhost.something', {
headers,
body,
})
.then((response) => {
response.json();
})
.then((response) => {
setToken(response.access_token);
})
.catch((e) => {
setError(e);
})
.finally(() => {
setLoading(false);
});
}, []);
return { fetchToken, token, error, loading };
};
what I am trying to find is a way to test that the output I have is the correct one in case of success and in case of error.
Seems like I can mock until the first .then but then i dont know how to mock the second one.
import { renderHook, act } from '#testing-library/react-hooks';
describe('useGetToken', () => {
it('should fetch and return a token', () => {
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
json: () => ({ access_token: 'aToken' }),
}),
);
const { result } = renderHook(() => useGetToken());
// also how to check for the fetchToken function to equal to itself i dont know how to do
// or maybe i can check if it is just a function
expect(result.current).toEqual({ token: 'aToken', loading: false, error: false });
});
});
managed a way to fix it changing with async await and the correct act.
also changed the double .then in the file to this
.then((response) => {
const parsedResponse = response.json();
setToken(parsedResponse.access_token);
})
cause i didnt need two
import { renderHook, act } from '#testing-library/react-hooks';
import useGetToken from '../useGetVfsToken';
describe('useGetToken', () => {
it('should fetch when fetchToken is called', async () => {
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
json: () => ({ access_token: 'aToken123' }),
}),
);
const { result } = renderHook(() => useGetToken());
expect(window.fetch).not.toHaveBeenCalled();
await act(async () => {
result.current.fetchToken();
});
expect(window.fetch).toHaveBeenCalledTimes(1);
expect(result.current.token).toEqual('aToken123');
expect(result.current.loading).toEqual(false);
expect(result.current.error).toBeUndefined();
});
it('should have an error', async () => {
const error = 'an error text';
global.fetch = jest.fn().mockImplementation(() => Promise.reject(error));
const { result } = renderHook(() => useGetToken());
await act(async () => {
result.current.fetchToken();
});
expect(result.current.error).toEqual(error);
expect(result.current.loading).toEqual(false);
});
});

Multiple API calls with Promise.all and dispatch an action

I want to call multiple API's and store each response data in an object then I want to dispatch this response object but I'm getting undefined.
Below is the code I tried. May I know where I'm doing wrong?
/* COMPONENT.JSX */
componentDidMount() {
callApis(this.props.products, this.props.profileId);
}
/* API.JS */
const getContactDetails = (http, profileId) =>
(http.get(`https://www.fakeurl.com/${profileId}/contact`));
const getProductDetails = (http, profileId) =>
(http.get(`https://www.fakeurl.com/${profileId}/product`));
const callApis = (products, profileId) => (dispatch) => {
const payload = new Map();
products.forEach((product) => {
const apis = [getContactDetails, getProductDetails];
apis.map(api => api(http, profileId));
Promise.all(apis)
.then((response) => {
const apiData = {
contactData: getParsedContactData(response[0]),
productData: getParsedProductData(response[1])
};
if (payload.get(product.token)) {
payload.get(companion.token).push(apiData);
} else {
payload.set(product.token, [apiData]);
}
})
.catch(err => {
throw ('An error occurred ', err);
});
});
dispatch({ type: FETCH_API_DATA, payload: payload });
}
I expect the dispatch will be called after all API's were resolved, get parsed, and map into the payload object then it should dispatch.
Array.map returns a new Array, which you are discarding
you're calling dispatch before any of the asynchronous code has run
A few minor changes are required
/* API.JS */
const getContactDetails = (http, profileId) => http.get(`https://www.fakeurl.com/${profileId}/contact`);
const getProductDetails = (http, profileId) => http.get(`https://www.fakeurl.com/${profileId}/product`);
const callApis = (products, profileId) => (dispatch) => {
const payload = new Map();
// *** 1
const outerPromises = products.map((product) => {
const apis = [getContactDetails, getProductDetails];
// *** 2
const promises = apis.map(api => api(http, profileId));
// *** 3
return Promise.all(promises)
.then((response) => {
const apiData = {
contactData: getParsedContactData(response[0]),
productData: getParsedProductData(response[1])
};
if (payload.get(product.token)) {
payload.get(companion.token).push(apiData);
} else {
payload.set(product.token, [apiData]);
}
})
.catch(err => {
throw ('An error occurred ', err);
});
}));
// *** 4
Promise.all(outerPromises)
.then(() => dispatch({
type: FETCH_API_DATA,
payload: payload
})
)
.catch(err => console.log(err));
}
rather than procucts.forEach, use products.map
capture the promises in apis.map to use in Promise.all
return Promise.all so the outer Promises can be waited for
Promise.all on the outer promises, to wait for everything to complete.
const callApis = (products, profileId) => async (dispatch) => { // use async function
const payload = new Map();
for (const product of products) {
const apis = [getContactDetails, getProductDetails];
apis.map(api => api(http, profileId));
await Promise.all(apis) // await all promise done
.then((response) => {
const apiData = {
contactData: getParsedContactData(response[0]),
productData: getParsedProductData(response[1])
};
if (payload.get(product.token)) {
payload.get(companion.token).push(apiData);
} else {
payload.set(product.token, [apiData]);
}
})
.catch(err => {
throw ('An error occurred ', err);
});
}
dispatch({ type: FETCH_API_DATA, payload: payload }); // dispatch will be executed when all promise done
}

Async and Await not working in Axios React

i have a problem:
I want that my axios make the requistion and after it makes the this.setState with the result saved in a variable.
My code:
componentDidMount() {
let mails = [];
axios.get('/api/employee/fulano')
.then(res => this.setState({
employees: res.data
}, () => {
this.state.employees.map(i => {
async axios.get(`/api/status/${i.mail}`)
.then(res => {
mails.push(res.data)
await this.setState({
mails: mails
})
})
.catch(err => console.log(err))
})
}))
.catch(err => console.log(err))
}
But it gives error syntax.
Best explanation: I want saved all results of the map in the variable mails and later to use the setState to changes the result of just a time.
Someone could tell me where i'm wandering? Please.
You are using async await at the wrong places. async keyword must be used for a function that contains asynchronous function
await keyword needs to be used for an expression that returns a Promise, and although setState is async, it doesn't return a Promise and hence await won't work with it
Your solution will look like
componentDidMount() {
let mails = [];
axios.get('/api/employee/fulano')
.then(res => this.setState({
employees: res.data
}, async () => {
const mails = await Promise.all(this.state.employees.map(async (i) => { // map function contains async code
try {
const res = await axios.get(`/api/status/${i.mail}`)
return res.data;
} catch(err) {
console.log(err)
}
})
this.setState({ mails })
}))
.catch(err => console.log(err))
}
It's not a good practice to mix async/await with .then/.catch. Instead use one or the other. Here's an example of how you could do it using ONLY async/await and ONLY one this.setState() (reference to Promise.each function):
componentDidMount = async () => {
try {
const { data: employees } = await axios.get('/api/employee/fulano'); // get employees data from API and set res.data to "employees" (es6 destructing + alias)
const mails = []; // initialize variable mails as an empty array
await Promise.each(employees, async ({ mail }) => { // Promise.each is an asynchronous Promise loop function offered by a third party package called "bluebird"
try {
const { data } = await axios.get(`/api/status/${mail}`) // fetch mail status data
mails.push(data); // push found data into mails array, then loop back until all mail has been iterated over
} catch (err) { console.error(err); }
})
// optional: add a check to see if mails are present and not empty, otherwise throw an error.
this.setState({ employees, mails }); // set employees and mails to state
} catch (err) { console.error(err); }
}
This should work:
componentDidMount() {
axios.get('/api/employee/fulano')
.then(res => this.setState({
employees: res.data
}, () => {
this.state.employees.map(i => {
axios.get(`/api/status/${i.mail}`)
.then( async (res) => { // Fix occurred here
let mails = [].concat(res.data)
await this.setState({
mails: mails
})
})
.catch(err => console.log(err))
})
}))
.catch(err => console.log(err))
}
You put async in the wrong place
async should be placed in a function definition, not a function call
componentDidMount() {
let mails = [];
axios.get('/api/employee/fulano')
.then(res => this.setState({
employees: res.data
}, () => {
this.state.employees.map(i => {
axios.get(`/api/status/${i.mail}`)
.then(async (res) => {
mails.push(res.data)
await this.setState({
mails: mails
})
})
.catch(err => console.log(err))
})
}))
.catch(err => console.log(err))
}

Why won't my fetch response by passed in my dispatch?

I'm trying to use a node server as the intermediary between firebase and my react native app.
Can someone please tell me what I'm doing wrong here with my fetch?
export const fetchPostsByNewest = () => {
return (dispatch) => {
fetch('http://localhost:3090/')
.then((response) => {
console.log(response.json());
//dispatch({ type: NEW_POSTS_FETCH_SUCCESS, payload: response.json()});
});
};
};
this is the node/express router:
const router = (app) => {
app.get('/', (req, res) => {
firebase.database().ref('/social/posts')
.once('value', snapshot => res.json(snapshot.val()));
});
};
If I console.log the response.json() then I just get this:
If I console.log response, I get this:
How can I get rid of the headers? If I do console.log(response._bodyInit) then I get this:
Which looks like what I need.
But if I pass it through as payload then I get this error:
My previous action creator directly worked with firebase, like this:
export const fetchPostsByNewest = () => {
return (dispatch) => {
firebase.database().ref('/social/posts')
.once('value', snapshot => {
console.log(snapshot.val())
dispatch({ type: NEW_POSTS_FETCH_SUCCESS, payload: snapshot.val() });
});
};
};
If I console.log(snapshot.val()). Then I got this:
This works just fine and looks the same as the last console.log of console.log(response._bodyInit).
What am I doing wrong here? I'd really appreciate any help.
Thank you!
response.json() returns a promise, so you need another link in the Promise chain
export const fetchPostsByNewest = () => {
return (dispatch) => {
fetch('http://localhost:3090/')
.then((response) => response.json())
.then((json) => {
console.log(json);
dispatch({ type: NEW_POSTS_FETCH_SUCCESS, payload: json});
});
};
};
If you don't need to console.log the json
export const fetchPostsByNewest = () => {
return (dispatch) => {
fetch('http://localhost:3090/')
.then((response) => response.json())
.then((json) => dispatch({ type: NEW_POSTS_FETCH_SUCCESS, payload: json}));
};
};
Fetch works a little different, when you call response.json() that returns another promise, so you need to chain another then after you return the new promise:
fetch('http://localhost:3090/')
.then(response => response.json())
.then(aFunctionToDoSomethingWithTheJson);

Categories

Resources