ReactJS setState when all nested Axios calls are finished - reactjs

I have a problem with updating my state from nested axios call inside forEach loop:
constructor(props) {
super(props);
this.state = {
isLoaded: false,
items: []
};
//Binding fetch function to component's this
this.fetchFiles = this.fetchFiles.bind(this);
}
componentDidMount() {
this.fetchFiles();
}
fetchFiles() {
axios.get('/list')
.then((response) => {
var items = response.data.entries;
items.forEach((item, index) => {
axios.get('/download'+ item.path_lower)
.then((response) => {
item.link = response.data;
})
.catch(error => {
console.log(error);
})
});
this.setState(prevState => ({
isLoaded: true,
items: items
}));
console.log(this.state.items);
})
.catch((error) => {
console.log(error);
})
}
The idea is to get all items from Dropbox using it's API (JavaScript SDK)
and then for each item I also need to call different API endpoint to get a temporary download link and assign it as a new property. Only after all items will get their links attached I want to setState and render the component. Could somebody please help with this, I spend already multiple hours fighting with promises :S

You could use Promise.all to wait for multiple promises. Also keep in mind that setState is async and you wont see immediate changes. You need to pass a callback.
fetchFiles() {
axios.get('/list')
.then((response) => {
var items = response.data.entries;
// wait for all nested calls to finish
return Promise.all(items.map((item, index) => {
return axios.get('/download'+ item.path_lower)
.then((response) => {
item.link = response.data;
return item
});
}));
})
.then(items => this.setState(prevState => ({
isLoaded: true,
items: items
}), () => console.log(this.state.items)))
.catch((error) => {
console.log(error);
})
}

Try making the fetchfiles() function as an asynchronous method by adding the async keyword.Now, we have to wait till the items to get their download link, so add a await keyword before that line which makes the code to wait till the axios call gets completed.
async function fetchFiles() {
axios.get('/list')
.then(async function(response){
var items = response.data.entries;
await items.forEach((item, index) => {
axios.get('/download'+ item.path_lower)
.then((response) => {
item.link = response.data;
})
.catch(error => {
console.log(error);
})
});
this.setState(prevState => ({
isLoaded: true,
items: items
}));
console.log(this.state.items);
})
.catch((error) => {
console.log(error);
})
}
I haven't tested the code, but it should probably work.

Related

React JS fetch data from multiple API function

I was tasked to create a front-end for a poc. I am not a front end developer but I chose to use React JS.
There is only one page fetching data from multiple API endpoints. API endpoints return a simple json object.
I managed to get that to work however my code is ugly af and I want to create a function to handle all of that but I can't seem to get it right. Here's my code
export default class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
group1: [],
group2: [],
group3: [],
isLoaded: false,
}
}
componentDidMount() {
const group1_url = "http://localhost/api/1"
const group2_url = "http://localhost/api/2"
const group3_url = "http://localhost/api/3"
fetch(group1_url)
.then(res => res.json())
.then(json => {
this.setState({
group1: json,
})
});
fetch(group2_url)
.then(res => res.json())
.then(json => {
this.setState({
group2: json,
})
});
fetch(group3_url)
.then(res => res.json())
.then(json => {
this.setState({
group3: json,
})
});
}
I am trying to create a function like this:
function fetch_data(url, state) {
fetch(url)
.then(res => res.json())
.then(json => {
this.setState({
state: json,
})
});
}
var group1 = fetch_data(group1_url, group1);
So far no joy. How can I create a function to fetch data and set a state in js?
Alternatively how can I make my code look better? Or is there something else I should use/look into?
Pass a string as the second parameter, and use a computed property:
function fetch_data(url, state) {
fetch(url)
.then(res => res.json())
.then(json => {
this.setState({
[state]: json,
})
});
}
fetch_data(group1_url, 'group1');
I'd also highly recommend catching errors - possible unhandled rejections should be avoided whenever possible.
You might want to use Promise.all to wait for all groups to load:
const dataSources = {
group1: 'http://localhost/api/1',
group2: 'http://localhost/api/2',
group3: 'http://localhost/api/3',
};
Promise.all(
Object.entries(dataSources).map(([propertyName, url]) => (
fetch(url)
.then(res => res.json())
.then((result) => {
this.setState({
[propertyName]: result
})
})
))
)
.then(() => {
this.setState({ isLoaded: true })
})
.catch((error) => {
// handle errors
})
(also note that your json argument is not a JSON - JSON is only a format that exists with strings. Something that has been deserialized is just a plain object or array. Better to call it something less misleading, like result as I did)
You could try Promise.all
Promise.all takes an array of promises (it technically can be any iterable, but is usually an array) and returns a new promise.
const points = [
"http://localhost/api/1",
"http://localhost/api/2",
"http://localhost/api/3",
];
const responses = await Promise.all(points.map((point) => fetch(point)));
const data = await Promise.all(responses.map((response) => response.json()));
const [group1, group2, group3] = data;
this.setState({
group1,
group2,
group3,
});
Just remember to wrap this logic in an async function
You can do something like this.
function fetch_data(url, state) {
fetch(url)
.then(res => res.json())
.then(json => {
this.setState({
[state]: json,
})
});
}
var group1 = fetch_data(group1_url, 'group1');

Using JS native fetch() api in React ComponentDidMount() results in a pending Promise

I was trying to load data into my project from the public folder in the componentDidMount() lifecycle method. However, I didn't get the desired FeatureCollection Object but a pending Promise.
componentDidMount = () => {
...
const data = fetch(`vcd/${this.state.monthFile}`)
.then(response => response.text())
.then(async data => {
return csv2geojson.csv2geojson(data, {
latfield: 'lat',
lonfield: 'lng',
delimiter: ','
}, (err, data) => {
if (err) console.log(err);
console.log(data); // correctly outputs a FeatureCollection, length 30277
return data;
// this.setState({ someAttribute: data }) => Also doesn't work.
})
})
.then(data => data); // If to use another Promise chaining, the result would be undefined.
console.log(data); // a pending Promise
}
My file contains 30277 rows * 3 columns, ~500Kb in size, which I think shouldn't be a problem with data loading, and after consulting the csv2geojson and fetch API, I still can't think of a solution to this problem. I am grateful for any helpful inputs.
EDIT: Using both async-await pattern and chaining another .then would result in undefined.
JS Fetch returns a promise so its because you're returning that promise.
So just change your code like this it will work;
import React, { useEffect, useState } from "react";
export default function ExampleHooks() {
const [data, setData] = useState(null);
var csv2geojson = require("csv2geojson");
useEffect(() => {
fetch("https://gw3xz.csb.app/sample.csv")
.then((response) => response.text())
.then(async (data) => {
csv2geojson.csv2geojson(
data,
{
latfield: "lat",
lonfield: "lng",
delimiter: ","
},
(err, data) => {
if (err) console.log(err);
setData(data);
}
);
});
}, []);
return <div onClick={() => console.log(data)}>show data</div>;
}
or as a Class Component:
import React from "react";
var csv2geojson = require("csv2geojson");
class ExampleClass extends React.Component {
state = {
data: null
};
componentDidMount() {
fetch(`vcd/${this.state.monthFile}`)
.then((response) => response.text())
.then(async (data) => {
csv2geojson.csv2geojson(
data,
{
latfield: "lat",
lonfield: "lng",
delimiter: ","
},
(err, data) => {
if (err) console.log(err);
this.setState({ data: data });
}
);
});
}
render() {
return <div onClick={() => console.log(this.state.data)}>show data</div>;
}
}
export default ExampleClass;
Working example over here
fetch returns a promise, and that is what you save to data. If you want to log the "data", then you have a couple options.
Log it IN the promise chain (you already do that)
Convert over to async/await and await the fetch to resolve/reject
code
componentDidMount = async () => {
...
const data = await fetch(`vcd/${this.state.monthFile}`)
.then(response => response.text())
.then(data => {
return csv2geojson.csv2geojson(data, {
latfield: 'lat',
lonfield: 'lng',
delimiter: ','
}, (err, data) => {
if (err) console.log(err);
console.log(data);
return data;
})
});
console.log(data); // a resolved/rejected Promise result
}

Having multiple API calls in one componentDidMount - possible?

I've got a rather tricky problem. Within my componentDidMount method, I have:
1.) State being set on the variable 'dog'
2.) An API call being made via axios, the response of which sets the state of another variable, 'dogName'
This is creating problems (the data I want rendered to the browser isn't rendering) - so is there a better way to write my code?
setData = async () => {
const x = await fetch("https://dog.ceo/api/breed/hound/images");
const y = await x.json();
const z = await y.message;
let newArr = [];
for (let i = 0; i < z.length; i++) {
if (i <= 9) {
newArr.push(z[i]);
}
}
return newArr;
};
componentDidMount() {
this.setState({
loading:true
})
this.setData()
.then(res =>{
this.setState({
loading:false,
dog: res,
})
})
axios.get('http://localhost:3000/dogs')
.then(res => {
this.setState({
dogName:res.data
})
})
.catch(error => {
console.log(error)
})
}
this should do the trick
You listen on both promise resolves at the same time and then execute the setState with all data you got.
My tip for you is: you should look into react hooks or even react-redux to get data not directly in your component code :)
componentDidMount() {
this.setState({
loading: true,
});
const dogPromise = this.setData();
const dogNamePromise = axios.get('http://localhost:3000/dogs');
Promise.all([
dogPromise,
dogNamePromise
])
.then(([dogResponse, dogNameResponse]) => {
this.setState({
loading: false,
dog: dogResponse,
dogName: dogNameResponse.data,
});
})
.catch(error => {
console.log(error);
});
}

Assigning return values from api to state in React

Hi I am trying to call an api assign the returned values to a state object in React, the API is returning values but the values are not being set to state, not understanding what's the reason thank you
handleDDLCommunityChange = event => {
let filesFromApi = []; // ["file1", "file2", "file3", "file4"];
fetch('https://localhost:44352/api/files/Community-1')
.then((response) => {
return response.json();
})
.then(data => {
filesFromApi = data.map(file => { return { value: file, display: file } });
}).catch(error => {
console.log(error);
debugger;
});
console.log(filesFromApi);
this.setState({
files: filesFromApi.map(file => {
return {
fileName: file,
checked: false
};
})
});
};
fetch is an async method. An async method dispatches an action with the callbacks and unblocks following code branch from executing. The callbacks are then used to act on completion (success or failure) of the async method execution.
As you are calling the setState outside of the callbacks of the fetch call's chain, it's not guaranteed to run after the fetch call is done. As Sudheer has pointed out in their comment, you should try to set the state in a then block of the fetch chain.
warning: untested code
handleDDLCommunityChange = event => {
let filesFromApi = []; // ["file1", "file2", "file3", "file4"];
fetch('https://localhost:44352/api/files/Community-1')
.then(response => response.json())
.then(data => {
filesFromApi = data.map(file => ({ value: file, display: file });
this.setState({
files: filesFromApi.map(file => ({
fileName: file,
checked: false
})
})
});
}).catch(error => {
console.log(error);
debugger;
});
};

React setstate callback does not work after initial use

I have a function that, when initialized, takes a previously set state and uses it to make an api call with axios:
_onRefresh = () => {
this.setState({ refreshing: true }, () => {
axios.get(this.state.currentPath)
.then(res=>{
console.log(res.data)
this.props.loadCards(res.data)
})
this.setState({refreshing: false})
});
}
I can see that the promise is never completed, and that a response is not given.
However, on the first use after the page loads, the function works correctly; it's only on subsequent usage that it does not work.
When the get request does not work, I've taken the path that's been stored in state, made a request in postman, and received a valid result.
you should cancel the refreshing in the finally block
get(...)
.then(...)
.catch(...)
.finally(() => this.setState({refreshing: false}))
Does this fix the issue?
_onRefresh = () => {
this.setState({ refreshing: true }, () => {
axios.get(this.state.currentPath)
.then(res=>{
console.log(res.data)
this.props.loadCards(res.data)
this.setState({refreshing: false}) // moved this line into the then block
})
});
}
Try this
_onRefresh =async () => {
this.setState({ refreshing: true }, () => {
await axios.get(this.state.currentPath) //you might need to handle promises to get this working.
.then(res=>{
console.log(res.data)
this.props.loadCards(res.data)
})
this.setState({refreshing: false})
});
}

Resources