ReactJS componentDidMount does not produce the value before rendering - reactjs

I have the following code and getting the values through the api and set to the state variable but the view is rendered before setting the value to the state. So i could not display the value in my view. How could i change the code to work fine?
this.state = {
activeJobs: [],
isLoading: true
};
componentDidMount(){
axios.get(this.state.url+'/tables')
.then(response => {
// If request is good...
const isLoading = true,
activeJobs = response.data.activeJobs;
this.setState({ activeJobs });
})
.catch((error) => {
console.log('error ' + error);
});
}
render() {
console.log(this.state.activeJobs)
<p className="text">{!this.state.isLoading && this.state.activeJobs.count} Jobs</p>
}
The console i have given inside the render shows blank array. I also tried by changing the function componentDidMount() to componentWillMount() but getting the same result.

There is no way to ensure that an async request will complete before rendering. You can display proper messages in render to reflect the status of the request.
For example - before calling axios, set the state to 'in process' or 'loading', so that render will show an appropriate message. Then, when loading finished successfully or with an error, set the state appropriately to render an appropriate message in the error case, and the result otherwise.

If you can't render yet, then simply return null:
render() {
if (!this.state.activeJobs && !this.state.isLoading) {
return null;
}
return (
<div>
{ this.state.isLoading && <p className="text">Loading...</p> }
{ !this.state.isLoading && <p className="test">{ this.state.activeJobs.count } Jobs</p>
</div>
);
}
In order to set isLoading, set it before the HTTP call:
componentDidMount(){
this.setState({ isLoading: true });
axios.get(this.state.url+'/tables')
.then(response => {
// If request is good...
const activeJobs = response.data.activeJobs;
this.setState({ activeJobs, isLoading: false });
})
.catch((error) => {
console.log('error ' + error);
});
}

Related

React - get data with axios

In my react app that is based on class components, My response API got from open weather fixes after several lags.
this is my state
class Weather extends Component {
constructor(props) {
super(props);
this.state = {
weatherData: undefined,
weatherDescription: undefined,
};
}
My thinking was that when my componentDidMount,
weather API getting from openWeather and set it in state
componentDidMount() {
axios
.get(
`http://api.openweathermap.org/data/2.5/weather?id=someCityId&units=metric&appid=myApiKey`
)
.then((response) => {
if (response.request.status === 200) {
this.setState({
weatherData: response.data.main.temp,
weatherDescription: response.data.weather[0].description,
weatherTextDisplay: this.state.airConditionsText.filter((item)=>{
return item["id"] === response.data.weather[0].id
})
});
}else{throw Error('No internet')}
})
.catch(error => Error.message)
and I want to update data when the city is changing, in componentDidUpdate the data get again from the openWeather
componentDidUpdate() {
axios
.get(
`http://api.openweathermap.org/data/2.5/weather?id=someCityId&units=metric&appid=myApiKey`
)
.then((response) => {
if (response.request.status === 200) {
this.setState({
weatherData: response.data.main.temp,
weatherDescription: response.data.weather[0].description,
weatherTextDisplay: this.state.airConditionsText.filter((item)=>{
return item["id"] === response.data.weather[0].id
})
});
}else{throw Error('No internet')}
})
.catch(error => Error.message)
}
But the problem is that when my response receives, it faces a lag that causes data jumps several times to previous data and new data until it fixes
I do not completely understand the question, but this 'lags' because the action of fetching something from an external source is async and needs time to complete.
As for the second 'part' of displaying the loading text you have to set a variable (preferably in state which indicates the loading state of this component)
eg.
constructor(props) {
super(props);
this.state = {
loading: false,
airConditionsText: null,
// Other stuff you have in state
};
}
componentDidUpdate() {
this.setState({loading: true}) // Start of loading
axios
.get(
`http://api.openweathermap.org/data/2.5/weather?id=${this.state.inputId}&units=metric&appid=myApiKey`
)
.then((response) => {
if (response.request.status === 200) {
this.setState({
weatherData: response.data.main.temp,
weatherDescription: response.data.weather[0].description,
weatherTextDisplay: this.state.airConditionsText.filter((item)=>{
return item["id"] === response.data.weather[0].id
})
});
}else{throw Error('No internet')}
})
.catch(error => Error.message)
.finally(() => this.setState({loading: false})) // End of loading
.finally is being trigger once the async operation (fetching the data from weatherAPI) finishes with either error or success which is the time to stop loading.
Then you can use this.state.loading in component render to show loading text
eg.
render() {
return (
<div>
{this.state.loading
? <div> Loading... </div>
: <div>{this.state.airConditionsText}</div> // other stuff you want to display
}
</div>
);
}

React componentDidMount and working with Promises?

Getting really fed up now! I am trying to get a Spinner element to appear while 3 functions run in the componentDidMount function.
From what I gather the render comes before componentDidMount, so I am running the Spinner in the render, while:
a cookie value is retrieved from this.getValidToken()
then an axios post request sets state of isLoggedin (using above value as payload)
then the logic() function runs a simple if statement to either log user in or redirect to
error page.
I keep getting errors about Promises, I feel there is a better way to do this?
constructor(props){
super(props);
this.state = {
isLoggedIn: false
}
}
componentDidMount() {
const post =
axios.post(//api post request here)
.then(function(response) {
this.setState({ isLoggedIn: true });
})
.catch(function(error) {
this.setState({ isLoggedIn: false });
})
const LoggedIn = this.state.isLoggedIn;
const logic = () => {
if (LoggedIn) {
//log user in
} else {
//redirect user to another page
}
};
this.getValidToken()
.then(post)
.then(logic);
//getValidToken firstly gets a cookie value which is then a payload for the post function
}
render() {
return <Spinner />;
}
Firstly, you assign axios post to a variable, it is executed immediately and not after the getValidToken promise is resoved
Secondly the state update in react is async so you cannot have loggedIn logic based on state in promise resolver
You could handle the above scenario something like
constructor(props){
super(props);
this.state = {
isLoggedIn: false
}
}
componentDidMount() {
const post = () => axios.post(//api post request here)
.then(function(response) {
this.setState({ isLoggedIn: true });
return true;
})
.catch(function(error) {
this.setState({ isLoggedIn: false });
return false;
})
const logic = (isLoggedIn) => { // use promise chaining here
if (isLoggedIn) {
//log user in
} else {
//redirect user to another page
}
};
this.getValidToken()
.then(post)
.then(logic);
//getValidToken firstly gets a cookie value which is then a payload for the post function
}
render() {
return <Spinner />;
}

JSON Data Object Handling in React

Cant figure it out how to extract data from parsed and stored json object from state ... i have tried many ways but getting errors (data from openweathermap)
class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
data: null
};
}
getWeather(url) {
fetch(url)
.then(res => res.json())
.then(data =>
this.setState({
isLoading: false,
data: data
})
)
.catch(error => console.log("Error Loading data " + error));
}
componentDidMount() {
navigator.geolocation.getCurrentPosition(position => {
const url = `${API}lat=${position.coords.latitude}&lon=${position.coords.longitude}&${APPID}`;
this.getWeather(url);
});
}
render() {
return (
<div>
{console.log(this.state.data)}
{/*renders json*/}
<h1>{this.state.data.name}</h1>
</div>
);
}
}
I believe you might be getting cannot read property 'name' of null. Not sure about the exact error but something like that.
Try to use
<h1>{ this.state.data && this.state.data.name }</h1>
Till the time API is not giving the response, your data in the state is null so you cannot access data.name.

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;

Resources