Unable to setState regardless of making setState synchronous - reactjs

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!

Related

How to wait for the promise to resolve and update the state

This is what my code looks like:
constructor(props) {
super(props);
this.state = {
docs: []
};
}
async componentDidMount() {
await this.quizes();
console.log(this.state.docs);
}
quizes = () => {
firebase
.firestore()
.collection("quiz")
.get()
.then(result => {
const docs = result.docs.map(doc => {
return { uid: doc.id, ...doc.data() };
});
this.setState({ docs });
});
};
Currently console.log(this.state) returns empty docs when I am trying to update it with documents from firestore.
setState is asynchronous. If you are sure that your collection is not empty then you can see your state using:
this.setState({ docs }, () => console.log(this.state);
The function as second argument of setState is run only when the asynchronous task of setting the state is done, thus you are going to see the updated state.
In order to await your quizes function it also needs to be async and use the await syntax rather than promises.
For example this code should achieve the desired outcome:
constructor(props) {
super(props);
this.state = {
docs: []
};
}
async componentDidMount() {
await this.quizes();
}
quizes = async () => {
let result = await firebase.firestore().collection("quiz").get()
const docs = result.docs.map(doc => {
return { uid: doc.id, ...doc.data() }
});
return this.setState({ docs }, () => {
console.log(this.state.docs);
});
};
EDIT:
setState uses a callback. In order to guarantee the state has been set at the time of logging, use callback within the quizes function.

where should i call the setintreval in react

I am trying to call getSession() every 5sec of delay. But in initial render i would like to call this function and execute immediately.
According to my below code, in the initial render itself it is using the delay of 5sec to display the output.
How can i achieve the following:
1. Initial render should be done immediately
2. after every 5sec getSession() should be called as well.
Current Results:
It is taking 5sec delay to display in initial render.
Expected results:
Initial render should be done immediately.
componentDidMount() {
this.getSession();
}
getSession() {
var path = "Sharing.aspx/GetSessions";
setInterval(() => {
axios
.post(path, { withCredentials: true })
.then(response => {
let element = response.data.d;
this.setState({
sessions: element
});
})
.catch(error => {
this.setState({
Errors: error
});
console.error(error);
});
},5000
);
}
render() {
return (
<div>
{this.renderSessionDetails()}
</div>
);
}
Expected results:
Initial render should be done immediately.
After every 5sec getSessions() should be called.
I would do something like this:
const INTERVAL = 6000;
class Component extends React.Component {
componentDidMount() {
this.getSession();
this.intervalId = window.setInterval(() => this.getSession(), INTERVAL);
}
componentWillUnmount() {
window.clearInterval(this.intervalId);
}
getSession() {
var path = "Sharing.aspx/GetSessions";
setInterval(() => {
axios
.post(path, { withCredentials: true })
.then(response => {
let element = response.data.d;
this.setState({
sessions: element
});
})
.catch(error => {
this.setState({
Errors: error
});
console.error(error);
});
}, 5000);
}
render() {
return <div>{this.renderSessionDetails()}</div>;
}
}
ComponentDidMount will be called only once, and at that point, you call the first getSession call, and start the interval.
An important thing to bring attention to is the call to window.clearInterval when the component gets unmounted. This is to make sure that interval doesn't keep running eternally, and worst, that more than one interval run in parallel after having this component mount a couple of times.
I hope it helps.
You could go about refactoring your code to look like that, in order to avoid waiting initially for those 5 seconds. The refactor is mainly about extracting the fetching logic away from the timer implementation. Please note that inside componentDidMount() we first call this.getSession() immediately, which is fine because we eliminated the intervals from it. Then we dispatch the intervals.
class Component extends React.Component() {
intervalId = null
componentDidMount() {
this.getSession()
this.intervalId = setInterval(() => this.getSession(), 5000)
}
componentWillUnmount() {
if (this.intervalId) {
clearInterval(this.intervalId)
}
}
getSession() {
var path = 'Sharing.aspx/GetSessions'
axios
.post(path, { withCredentials: true })
.then(response => {
let element = response.data.d
this.setState({
sessions: element
})
})
.catch(error => {
this.setState({
Errors: error
})
console.error(error)
})
}
render() {
return <div>{this.renderSessionDetails()}</div>
}
}
I would also try to make sure we're not running into race conditions here. But, if you're sure your requests never take more than 5 seconds -- it should be fine. Hope it helps!

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.

React accessing state before ComponentDidMount

When I try to access a state variable which is set in ComponentDidMount, react throws an undefined error. This is because I believe when I'm calling the fetch api and setState in ComponentDidMount, the value isn't ready yet (async stuff). Is there a proper way to either delay the render until the setState call is done or some other way to get the state updated fully before render is called?
I think the code below will give you a basic idea how fetch data and render work.
class App extends Component {
state = {
data:{},
loading:true,
error:null,
}
componentDidMount = () => {
fetch('https://example.com/api/article')
.then((response) => {
return response.json();
})
.then((json) => {
this.setState({
data:json,
loading:false,
})
.catch(error => {
this.setState({
error,
loading:false,
})
});
});
}
render() {
const {data,error,loading} = this.state;
if(loading){
return "Loading ..."
}
if(error){
return "Something went wrong."
}
return 'your actual render component or data';
}
}
export default App;

React lifecycle methods: fetch in componentDidMount

I'm trying to do a simple fetch through the componentDidMount lifecycle method. However, the result does not appear on the page as it should unless I have a one second timeout. I've gathered it's due to the async nature of the fetch, but how can I fix that without having to use setTimeout? Would componentDidUpdate work/how would you use it?
constructor(props) {
super(props);
this.state = { value: '' };
this.getValue= this.getValue.bind(this);
}
getValue() {
return (
fetch(url, {
method: 'GET',
}).then(response => {
if (response.status >= 400) {
throw new Error('no response: throw');
}
return response.json()
}).then(response => {
this.setState({value: response});
}).catch((error) => {
this.setState({
value: 'no response: catch'
})
})
);
}
componentDidMount(){
//this.getValue(); //does not work
setTimeout(() => this.getValue(), 1000); //this works & populates page
}
render() {
return (
<div>
<div>{this.state.value}</div>
</div>
)
}
Be sure you are binding your this.getValue method to the proper context in the constructor. When you put it in your setTimeout, you have it in a fat arrow function which binds to this implicitly.
constructor(props) {
super(props);
this.getValue = this.getValue.bind(this);
}
getValue() { ... }
componentDidMount() {
this.getValue();
}

Resources