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;
Related
I'm using class-based components in react. I have few components named as follows: Blogs, BlogsClient, BlogCard. When Blogs mounts I make a call to a function inside BlogClient named as getBlogContent to fetch me data using axios.
setBlogs = (blogs) => {
this.setState({ "blogs": blogs });
}
componentDidMount() {
getBlogContent(this.setBlogs);
}
where getBlogContent is:
let getBlogContent = (setBlogs) => {
store.set('loaded', false);
axios.get(ADMIN_URL + '/getAllBlogs')
.then(response => {
store.set('loaded', true);
setBlogs(response.data.Response);
})
.catch(error => {
store.set('loaded', true);
store.set('errorMessage', error);
})
}
I'm able to fetch data and update my state properly. But If there comes any error inside Blogs or BlogCard(which is called inside Blogs) it goes inside the catch of getBlogContent whereas it should be only responsible for catching Axios error. What am I missing here?
Ok, so it's hard to tell without knowing these errors..
But nonetheless, you should avoid setting the component's state outside that component. So, your code'd become:
componentDidMount() {
const blogContent = getBlogContent();
if (blogContent !== 'error'j this.setBlogs(blogContent);
}
let getBlogContent = () => {
store.set('loaded', false);
return axios.get(ADMIN_URL + '/getAllBlogs')
.then(response => {
store.set('loaded', true);
return response.data.Response;
})
.catch(error => {
store.set('loaded', true);
store.set('errorMessage', error);
return 'error';
})
}
I have Posts component:
class ProfilePosts extends React.Component {
constructor(props){
super(props);
this.state = {
posts: []
}
}
getData = async (url) => {
const res = await fetch(url, {
method: 'GET',
headers: {
'Authorization' : `${window.localStorage.getItem('token')}`
}
});
return await res.json();
}
getPosts = async () => {
if (window.location.pathname === '/profile/undefined%7D')
{
window.location.pathname = `/profile/${window.localStorage.getItem('id')}`
}
await this.getData(`https://api.com/posts/${window.location.pathname.slice(9)}`)
.then(data => {
this.setState({posts: data}
);
})
}
componentDidMount(){
if (window.localStorage.getItem('id') != ''){
this.getPosts();
}
}
componentDidUpdate(){
if (window.localStorage.getItem('id') != ''){
this.getPosts();
}
}
renderItems(posts){
return Object.values(posts).map(post => {
return (
<ProfilePost likes={post.likes} comments={post.comments} userId={window.localStorage.getItem('id')} token={window.localStorage.getItem('token')} Postid={post.id} key={post.id} sender={post.sender} content={post.content} time={post.sent_time}/>
)
});
}
render() {
const {posts} = this.state;
const items = this.renderItems(posts);
return(
<div className="profile-posts">
{items}
</div>
);}
}
export default ProfilePosts;
I don't understand what is the reasons for re-rendering this component so many times (I don't know the actual number but it's huge). Therefore, a lot of requests to the database, and so on. How can I fix this?
I tried using another state but that didn't work for me. If you press the button, the state changed and then I checked if the state changed then I call this.getPosts() and then set the state to false again. It caused Error: Maximum update depth exceeded.
Your componentDidUpdate runs every time the component updates, after render, so it's calling this.getPosts();, which in turn calls setState, which results in another re-render and update, and so on.
Remove the componentDidUpdate entirely and let the componentDidMount method (which implements the same logic) alone take care of things.
i'm new in react and i've got some issues with asynchronous fetch data :
i want to fetch github users
function fetchUser(username) {
return fetch(`https://api.github.com/users/${username}`)
.then(response => response.json())
.then(data => data)
}
export function getUserData(username) {
const object = {
profile: fetchUser(username),
}
console.log(object)
return object
}
and this is my method in my component
componentDidMount() {
getUserData(this.props.playerOne)
}
but this is what i got in my console
{profile: Promise}
i'm sure that i dont understand well this promise so could you help me to have not a Promise in my object but the data i'm fetching ? (if i log data in my fetch i got what i want)
You can make this function async and wait for the promise resolution.
export async function getUserData(username) {
const object = {
profile: await fetchUser(username),
}
console.log(object)
return object
}
then in your componentDidMount
componentDidMount() {
getUserData(this.props.playerOne)
.then((profile) => { this.setState({ profile }) })
}
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!
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!