maintaining couple actions in react - loading screen - reactjs

In my app component I am fetching couple things so there's couple actions. It's a state component. When one of the actions ends isLoading property changes to false and screen loading disappears. But it doesn't work properly because one action can take longer than another. How Can I change my isLoading property to false after all async actions are done?
My code looks something like
componentDidMount() {
this.props.fetchA();
this.props.fetchB();
this.props.fetchC().then(() => {
this.setState({isLoading: false})
})
}

You can chain those promises like this
componentDidMount() {
this.setState({ isLoading: true}); // start your loader
this.props.fetchA()
.then(() => {
return this.props.fetchB();
})
.then(() => {
return this.props.fetchC()
})
.then(() => {
this.setState({ isLoading: false }); // Once done, set loader to false
})
.catch(error => {
console.log('Oh no, something went wrong', error);
});
}
or using async/await with try catch do something fancy like this.
constructor () {
super();
this.state = {
isLoading: false,
};
this.onLoadData = this.onLoadData.bind(this); // see what I did here, i binded it with "this"
}
componentDidMount() {
this.onLoadData(); // Call you async method here
}
async onLoadData () {
this.setState({ isLoading: true}); // start your loader
try {
const awaitA = await this.props.fetchA();
const awaitB = await this.props.fetchB();
const awaitC = await this.props.fetchC();
this.setState({ isLoading: false }); // Once done, set loader to false
} catch (e) {
console.log('Oh no, something went wrong', error);
}
}

Related

Unable to setState regardless of making setState synchronous

I am learning about how to use synchronous setState but it is not working for my project. I want to update the state after I get the listingInfo from Axios but it does not work, the res.data, however, is working fine
class ListingItem extends Component {
constructor(props) {
super(props);
this.state = {
listingInfo: {},
open: false,
};
this.getListingData(this.props.itemId);
}
setStateSynchronous(stateUpdate) {
return new Promise((resolve) => {
this.setState(stateUpdate, () => resolve());
});
}
getListingData = async (item_id) => {
try {
const res = await axios.get(`http://localhost:5000/api/items/${item_id}`);
console.log(res.data);//it's working
await this.setStateSynchronous({ listingInfo: res.data });
// this.setState({
// listingInfo: res.data,
// });
console.log(this.state.listingInfo);//no result
} catch (err) {
setAlert('Fail to obtain listings', 'error');
}
};
I would be really grateful for your help!
Thanks to #PrathapReddy! I used conditional rendering to prevent the data from rendering before the setState is done. I added this line of code on the rendering part:
render() {
if (Object.keys(this.state.listingInfo).length === 0) {
return (
<div>
Loading
</div>
);
} else {
return //put what you want to initially render here
}
}
Also, there is no need to modify the setState, the normal setState will do. Hope this is useful!

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);
});
}

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})
});
}

How to Consume an API & Display Results in a List

I'm creating a trivia game using ReactJS, and the following API endpoint: https://opentdb.com/api.php?amount=5&difficulty=medium&type=boolean
From my understanding, I want to consume the API in ComponentDidMount() lifecycle method. From there I'm trying to map() over each item in this response (there should be 5 questions in total), and save them to an empty questions array (which is part of my component's state). Finally, I want to display these questions in a list.
I've tried all sorts of angles including async await, fetch() & .then(), using axios.get, etc. Here's an example of what I've been trying most recently. I'm just not sure how to consume the API, save those questions to the empty questions array to state, and then iterate over, and render the questions in the DOM.
Please note: I have tried console.log(this.state.questions), which shows the response as code 200, and my original API endpoint URL, but no questions data. I don't understand where the questions are at this point! Please help!
class App extends React.Component{
constructor(props){
super(props)
this.state = {
questions: [],
score: 0,
current: 0,
loading: false
}
}
async componentDidMount() {
try {
this.setState({ loading: true })
this.setState({ questions: await fetch('https://opentdb.com/api.php?amount=5&difficulty=medium&type=boolean'), loading: false })
console.log("state", this.state.questions);
console.log("props", this.props.questions);
} catch (err) {
console.log(err)
}
}
}
componentDidUpdate(){
console.log("component just updated!");
}
// another ComponentDidMount Attempt
// async componentDidMount(){
// try {
// this.setState({loading:true})
// this.setState({questions: await fetch('https://opentdb.com/api.php?amount=5&difficulty=medium&type=boolean'), loading: false})
// .then(questions => questions.json())
// .then(questions => this.setState({questions, loading:false}))
// console.log("state", this.state.questions);
// console.log("props", this.props.questions);
// } catch(err) {
// console.log(err)
// }
//
// }
// attempt with axios ()
// componentDidMount() {
// axios
// .get("https://opentdb.com/api.php?amount=5&difficulty=medium&type=boolean")
// .then(response => {
// this.setState({ question: response.data.question });
// this.setState({ category: response.data.category });
// // this.setState({ type: response.data.type });
// // this.setState({ url: response.data.url });
// // this.setState({ score: response.data.score });
// console.log("axios GET worked");
// })
// .catch(err => {
// console.log(
// "Oops, something broke with GET in componentDidMount() - we've got a: ",
// err.message
// );
// });
// }
// below is having an issue with .map() - maybe bc questions
// is object containing arrays(?)
//
// render() {
// return (
// <div>
// {this.state.loading
// ? "loading..."
// : <div>
// {
// this.state.questions.map(question => {
// return(
// <div>
// <h3>Category: {question.category}</h3>
// <h4>Question: {question.question}</h4>
// </div>
// )
// })
// }
// </div>
// }
//
// <HomeCard />
// <QuizCard />
// <ResCard />
// </div>
// );
// }
export default App;
Try with
async componentDidMount() {
this.setState({
loading: true
});
try {
const response = await fetch('https://opentdb.com/api.php?amount=5&difficulty=medium&type=boolean');
const data = await response.json();
this.setState({
questions: data.results,
loading: false
});
} catch (err) {
console.log(err)
}
}
demo at https://codesandbox.io/s/sad-bogdan-g5mub
Your code doesn't work because the call to setState is asynchronous too and because of that your console.log(this.state.question); is executed before of the state update. In order to fix the problem, you can pass a callback as the second argument to setState, this callback will be executed after the state update.
It should look like this:
this.setState(
{
questions: await fetch('https://opentdb.com/api.php amount=5&difficulty=medium&type=boolean'),
loading: false
},
() => {
console.log("questions", this.state.questions);
console.log("loading", this.state.loading);
}
)
You can find more info about here: https://reactjs.org/docs/faq-state.html#why-is-setstate-giving-me-the-wrong-value.
I hope that this helps you.

ReactJS setState when all nested Axios calls are finished

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.

Resources