Unable to render content from api call in componentDidMount() - reactjs

This is a simplified version of a problem I've faced while working in React. When I'm making a fetch call inside componentDidMount() and updating the state with the payload as follows:
componentDidMount(){
fetch("/api/endpoint").then(data => {
return data.json();
}).then(json => {
this.setState({
data: json
});
});
}
And rendering it out in render():
render(){
return(
<p>{this.state.data.title}</p>
)
}
I get an error saying this.state.data is undefined. I got around it by wrapping it around a conditional operator as follows:
{ this.state.data !== undefined ? <p>{this.state.data.title}</p> : null }
But my question is, if componentDidMount() fires before render() then how can this.state.data ever be undefined?

componentDidMount() runs after render(). You need to initialize the state correctly in the constructor
class YourComponent extends React.Component {
constructor(props) {
super(props);
this.state = { data: {title: "" } };
}
componentDidMount(){
fetch("/api/endpoint").then(data => {
return data.json();
}).then(json => {
this.setState({
data: json
});
});
}
render(){
return(
<p>{this.state.data.title}</p>
)
}
}

But my question is, if componentDidMount() fires before render() then how can this.state.data ever be undefined?
The order of componentDidMount firing before or after render doesn't really matter here. componentDidMount has async effects. Let's say it always fires before render, then what happens is this:
componentDidMount is run. It runs fetch. Running fetch doesn't make the browser wait for fetch to return and do setState. Instead fetch sends the request to the server, and returns a promise that will be fulfilled when the response from the server returns.
The code that runs when the promise is fulfilled is the function you pass as an argument to .then(). Since your setState call is inside .then(), it will only run once the response it available.
Meanwhile, your browser goes ahead and calls render. The response from the server may or may not have returned and resolved the promise (most probably it won't have returned because network is slower than the processor executing code). So render gets called with this.state.data not yet defined. So you need to consider a state where data is undefined in render.

But my question is, if componentDidMount() fires before render() then
how can this.state.data ever be undefined?
You assumption/understanding that componentDidMount lifecycle function fires before render is wrong, componentWillMount is fired before render while componentDidMount is fired after the render method. Now you might say that since componentWillMount is fired before render, I can move my fetch call in componentWillMount and thus I would not have to add a check in the render method, but that not right. Even though your request is fired before the render the response may not always be available before the render function is executed and since all of these happen asynchronously you need to add a check for the data in render method
render(){
return(
<p>{this.state.data? this.state.data.title: null}</p>
)
}
Check Use componentWillMount or componentDidMount lifecycle functions for async request in React question for more details

Related

How read array/object from get Axios in React function [duplicate]

I have recently moved from Angular to ReactJs. I am using jQuery for API calls. I have an API which returns a random user list that is to be printed in a list.
I am not sure how to write my API calls. What is best practice for this?
I tried the following but I am not getting any output. I am open to implementing alternative API libraries if necessary.
Below is my code:
import React from 'react';
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
person: []
};
}
UserList(){
return $.getJSON('https://randomuser.me/api/')
.then(function(data) {
return data.results;
});
}
render() {
this.UserList().then(function(res){
this.state = {person: res};
});
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">
{this.state.person.map((item, i) =>{
return(
<h1>{item.name.first}</h1>
<span>{item.cell}, {item.email}</span>
)
})}
<div>
</div>
)
}
}
In this case, you can do ajax call inside componentDidMount, and then update state
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
this.UserList();
}
UserList() {
$.getJSON('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
render() {
const persons = this.state.person.map((item, i) => (
<div>
<h1>{ item.name.first }</h1>
<span>{ item.cell }, { item.email }</span>
</div>
));
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">{ persons }</div>
</div>
);
}
}
You may want to check out the Flux Architecture. I also recommend checking out React-Redux Implementation. Put your api calls in your actions. It is much more cleaner than putting it all in the component.
Actions are sort of helper methods that you can call to change your application state or do api calls.
Use fetch method inside componentDidMount to update state:
componentDidMount(){
fetch('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
This discussion has been for a while and #Alexander T.'s answer provided a good guide to follow for newer of React like me. And I'm going to share some additional know-how about calling the same API multiple times to refresh the component, I think it's probably a common question for beginners.
componentWillReceiveProps(nextProps), from official documentation :
If you need to update the state in response to prop changes (for
example, to reset it), you may compare this.props and nextProps and
perform state transitions using this.setState() in this method.
We could conclude that here is the place we handle props from the parent component, have API calls, and update the state.
Base on #Alexander T.'s example:
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
//For our first load.
this.UserList(this.props.group); //maybe something like "groupOne"
}
componentWillReceiveProps(nextProps) {
// Assuming parameter comes from url.
// let group = window.location.toString().split("/")[*indexParameterLocated*];
// this.UserList(group);
// Assuming parameter comes from props that from parent component.
let group = nextProps.group; // Maybe something like "groupTwo"
this.UserList(group);
}
UserList(group) {
$.getJSON('https://randomuser.me/api/' + group)
.then(({ results }) => this.setState({ person: results }));
}
render() {
return (...)
}
}
Update
componentWillReceiveProps() will be deprecated.
Here are only some methods (all of them in Doc) in the life cycle I think that they are related to deploying API in the general cases:
By referring to the diagram above:
Deploy API in componentDidMount()
The proper scenario to have API call here is that the content (from the response of API) of this component will be static, componentDidMount() only fire once while the component is mounting, even new props are passed from the parent component or have actions to lead re-rendering.
The component do check difference to re-render but not re-mount.
Quote from doc:
If you need to load data from a remote endpoint, this is a good place to
instantiate the network request.
Deploy API in static getDerivedStateFromProps(nextProps, prevState)
We should notice that there are two kinds of component updating, setState() in current component would not trigger this method but re-rendering or new props from parent component would.
We could find out this method also fires while mounting.
This is a proper place to deploy API if we want to use the current component as a template, and the new parameters to make API calls are props coming from parent component.
We receive a different response from API and return a new state here to change the content of this component.
For example:
We have a dropdown list for different Cars in the parent component, this component needs to show the details of the selected one.
Deploy API in componentDidUpdate(prevProps, prevState)
Different from static getDerivedStateFromProps(), this method is invoked immediately after every rendering except the initial rendering. We could have API calling and render difference in one component.
Extend the previous example:
The component to show Car's details may contain a list of series of this car, if we want to check the 2013 production one, we may click or select or ... the list item to lead a first setState() to reflect this behavior (such as highlighting the list item) in this component, and in the following componentDidUpdate() we send our request with new parameters (state). After getting the response, we setState() again for rendering the different content of the Car details. To prevent the following componentDidUpdate() from causing the infinity loop, we need to compare the state by utilizing prevState at the beginning of this method to decide if we send the API and render the new content.
This method really could be utilized just like static getDerivedStateFromProps() with props, but need to handle the changes of props by utilizing prevProps. And we need to cooperate with componentDidMount() to handle the initial API call.
Quote from doc:
... This is also a good place to do network requests as long as you
compare the current props to previous props ...
I would like you to have a look at redux
http://redux.js.org/index.html
They have very well defined way of handling async calls ie API calls, and instead of using jQuery for API calls, I would like to recommend using fetch or request npm packages, fetch is currently supported by modern browsers, but a shim is also available for server side.
There is also this another amazing package superagent, which has alot many options when making an API request and its very easy to use.
You can also fetch data with hooks in your function components
full example with api call: https://codesandbox.io/s/jvvkoo8pq3
second example: https://jsfiddle.net/bradcypert/jhrt40yv/6/
const Repos = ({user}) => {
const [repos, setRepos] = React.useState([]);
React.useEffect(() => {
const fetchData = async () => {
const response = await axios.get(`https://api.github.com/users/${user}/repos`);
setRepos(response.data);
}
fetchData();
}, []);
return (
<div>
{repos.map(repo =>
<div key={repo.id}>{repo.name}</div>
)}
</div>
);
}
ReactDOM.render(<Repos user="bradcypert" />, document.querySelector("#app"))
1) You can use Fetch API to fetch data from Endd Points:
Example fetching all Github repose for a user
/* Fetch GitHub Repos */
fetchData = () => {
//show progress bar
this.setState({ isLoading: true });
//fetch repos
fetch(`https://api.github.com/users/hiteshsahu/repos`)
.then(response => response.json())
.then(data => {
if (Array.isArray(data)) {
console.log(JSON.stringify(data));
this.setState({ repos: data ,
isLoading: false});
} else {
this.setState({ repos: [],
isLoading: false
});
}
});
};
2) Other Alternative is Axios
Using axios you can cut out the middle step of passing the results of
the http request to the .json() method. Axios just returns the data
object you would expect.
import axios from "axios";
/* Fetch GitHub Repos */
fetchDataWithAxios = () => {
//show progress bar
this.setState({ isLoading: true });
// fetch repos with axios
axios
.get(`https://api.github.com/users/hiteshsahu/repos`)
.then(result => {
console.log(result);
this.setState({
repos: result.data,
isLoading: false
});
})
.catch(error =>
this.setState({
error,
isLoading: false
})
);
}
Now you can choose to fetch data using any of this strategies in componentDidMount
class App extends React.Component {
state = {
repos: [],
isLoading: false
};
componentDidMount() {
this.fetchData ();
}
Meanwhile you can show progress bar while data is loading
{this.state.isLoading && <LinearProgress />}
Render function should be pure, it's mean that it only uses state and props to render, never try to modify the state in render, this usually causes ugly bugs and decreases performance significantly. It's also a good point if you separate data-fetching and render concerns in your React App. I recommend you read this article which explains this idea very well. https://medium.com/#learnreact/container-components-c0e67432e005#.sfydn87nm
This part from React v16 documentation will answer your question, read on about componentDidMount():
componentDidMount()
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. This method is a good place to set up
any subscriptions. If you do that, don’t forget to unsubscribe in
componentWillUnmount().
As you see, componentDidMount is considered the best place and cycle to do the api call, also access the node, means by this time it's safe to do the call, update the view or whatever you could do when document is ready, if you are using jQuery, it should somehow remind you document.ready() function, where you could make sure everything is ready for whatever you want to do in your code...
As an addition/update to Oleksandr T.'s excellent answer:
If you use class components, backend calls should happen in componentDidMount.
If you use hooks instead, you should use the effect hook
For example:
import React, { useState, useEffect } from 'react';
useEffect(() => {
fetchDataFromBackend();
}, []);
// define fetchDataFromBackend() as usual, using Fetch API or similar;
// the result will typically be stored as component state
Further reading:
Using the Effect Hook in the official docs.
How to fetch data with React Hooks? by Robin Wieruch
A clean way is to make an asynchronous API call inside componentDidMount with try/catch function.
When we called an API, we receive a response. Then we apply JSON method on it, to convert the response into a JavaScript object. Then we take from that response object only his child object named "results" (data.results).
In the beginning we defined "userList" in state as an empty array. As soon as we make the API call and receive data from that API, we assign the "results" to userList using setState method.
Inside the render function we tell that userList will be coming from state. Since the userList is an array of objects we map through it, to display a picture, a name and a phone number of each object "user". To retrieve this information we use dot notation (e.g. user.phone).
NOTE: depending on your API, your response may look different. Console.log the whole "response" to see which variables you need from it, and then assign them in setState.
UserList.js
import React, { Component } from "react";
export default class UserList extends Component {
state = {
userList: [], // list is empty in the beginning
error: false
};
componentDidMount() {
this.getUserList(); // function call
}
getUserList = async () => {
try { //try to get data
const response = await fetch("https://randomuser.me/api/");
if (response.ok) { // ckeck if status code is 200
const data = await response.json();
this.setState({ userList: data.results});
} else { this.setState({ error: true }) }
} catch (e) { //code will jump here if there is a network problem
this.setState({ error: true });
}
};
render() {
const { userList, error } = this.state
return (
<div>
{userList.length > 0 && userList.map(user => (
<div key={user}>
<img src={user.picture.medium} alt="user"/>
<div>
<div>{user.name.first}{user.name.last}</div>
<div>{user.phone}</div>
<div>{user.email}</div>
</div>
</div>
))}
{error && <div>Sorry, can not display the data</div>}
</div>
)
}}
As best place and practice for external API calls is React Lifecycle method componentDidMount(), where after the execution of the API call you should update the local state to be triggered new render() method call, then the changes in the updated local state will be applied on the component view.
As other option for initial external data source call in React is pointed the constructor() method of the class. The constructor is the first method executed on initialization of the component object instance. You could see this approach in the documentation examples for Higher-Order Components.
The method componentWillMount() and UNSAFE_componentWillMount() should not be used for external API calls, because they are intended to be deprecated. Here you could see common reasons, why this method will be deprecated.
Anyway you must never use render() method or method directly called from render() as a point for external API call. If you do this your application will be blocked.
You must try "axios" library for API call.
Instead of direct using jQuery.
Thanks.
It would be great to use axios for the api request which supports cancellation, interceptors etc. Along with axios, l use react-redux for state management and redux-saga/redux-thunk for the side effects.

How to fetch only one element

I'm trying to fetch data from api with axios, the problem that i want only one element with specific id but when i console.log i get hundred of lines of the same element
constructor(props) {
super();
const id= props.match.params.id;
this.state = {
items: [],
id: id,
}
}
render() {
axios.get(`http://api.tvmaze.com/shows/${this.state.id}`)
.then(res => {
this.setState({ items: res.data });
// console.log(this.state.items);
});
A React component gets rendered if a change happens in your props or in your state. If you update your state or your props, your UI element will get updated because the function render will be called, which causes multiple re-renderings.
Because of that, you need to move your calls to your API from your render function to another function, since your render function will potentially be called quite a few times and you don't want to send the same request to the API quite a few times. When using a class component, you can add your API call to the componentDidMount lifecycle function.
Move the axios.get to componentDidMount() lifecycle function and out of the render. As it currently is written, that will get called on every render, so every state, prop, or parent component render. If you move it to inside the lifecycle method it will call the get endpoint when the component mounts, just once.

React Native - setState doesn't work in constructor

Here is my code:
I want to set functions results which I get them as promises to some state variables but as it seems after use this.setState in then() function it returns empty object!
I checked in then() block and it contains data as it's supposed to.
class ProfileScreen extends Component{
constructor(props){
super(props);
this.state = {
profile_data: {},
user_data: {}
};
this._getUserData().then(response => {
this.setState({user_data: response});
});
//Empty Results
console.log(this.state.user_data);
this._getProfileData(this.state.user_data.id).then(response => {
this.setState({profile_data: response});
})
}
}
First of all you should never set your state in constructor using setState as a call to setState leads to rerendering of the view heirarchy which is not yet built to be rerendered.
A better place for doing so is componentDidMount because thats a lifecycle callback which ensures that the DOM is already rendered once and now you can update your state to see the changes in the view.
Coming to your problem, the empty result is due to the fact that the setState is asynchronous in nature, thus the console.log would have run even before the setState has updated the state.
You should rely on the second parameter of the setState which is a callback that runs post the state update.
this.setState({
user_data: response
}, () => {
console.log(this.state.user_data);
})
Don't do this in your constructor. Fetch your user detail in componentDidMount and then set your state as per your need
In addition to what Suraj said, if _getUserData is asynchronous, perhaps doing an AJAX call, then _getProfileData must be inside the .then callback too since you depend on the user_data to fetch the profile_data.
Try this snack out: https://snack.expo.io/#transfusion/stackoverflow-53694220
It might be cleaner to just chain the promises together.

React - wait the end of a request using Superagent

In ComponentWillMount I am fetching data from Node.js using Superagent.
componentWillMount() {
request.get("/appData").end((error, response) => {
if (!error && response) {
this.setState({ dataJSON: response.body });
} else {
console.log("There was an error fetching server", error);
}
});
}
This data is saved in this.state.dataJSON in order to render it in the render().
The problem is the render() is called before the setState() is effective.
But once the setState() updates the state of dataJSON the render is called a second time and render the data.
So I'm asking you a solution to wait for the update of dataJSON in order to call the render only when dataJSON is updated.
There is no such thing as delaying renders in React (... yet).
There're two things you can do:
load that state in the parent component and only mount the subcomponent when you have the data
make the illustrated component's render() check whether it's still loading and either show a loading message or return null if it's not ready.

ReactJs - state is rendered properly but inside method, shows wrong value

componentWillMount() {
console.log(this.state);
this.setState({
...this.state,
counter: this.state.counter + 1
});
console.log(this.state);
}
render() {
return (
{this.state.counter}
);
}
The output is clearly 1 in the screen
But both console.log is outputting 0.
I was doing a quiz app in reactjs. Same problem happened. Answered, correct Answers are always one less than the real values.
Calling setState doesn't immediately mutate the state.
Check the docs: https://reactjs.org/docs/react-component.html#setstate
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
To really check the updated state, you can do the following:
this.setState(Object.assign({}, this.state, {
counter: this.state.counter + 1
}), function () {
console.log(this.state.counter);
});
Also as per the documentation here: https://reactjs.org/docs/react-component.html#componentwillmount
componentWillMount() is invoked immediately before mounting occurs. It is called before render(), therefore calling setState() synchronously in this method will not trigger an extra rendering. Generally, we recommend using the constructor() instead.
Avoid introducing any side-effects or subscriptions in this method. For those use cases, use componentDidMount() instead.
the initial changes to state should be done in constructor as componentWillMount won't re-render your component. Or you are better off with componentDidMount.
I hope this helps.
setState is asynchronous , so there is no guarantee that you will see updated state value on the next line. If you want to make sure that the new value reflect in second console.
componentWillMount() {
console.log(this.state);
this.setState({
...this.state,
counter: this.state.counter + 1
}, () => {
console.log(this.state);
});
}
render() {
return (
{this.state.counter}
);
}
Your state is being changed for the first time before the component has been mount. After that the state isn't changed again so the render isn't triggered and therefore your counter isn't updated.
Try with componentDidMount instead.

Resources