ReactJs: Mulitiple Axios calls in componentDidMount() - javascript

I want to fetch data from my API (Spring Boot) to my front-end app on React, everything is working fine on the server-side.
Now I have a component named HomePage, where I want to fetch the array of Articles from my API. The problem is, somehow I am getting multiple responses from which the first and second ones are empty arrays, and the third one is the normal array with the needed data.
constructor(props){
super(props);
this.state = {
articles: []
}
}
componentDidMount(){
this.loadArticles();
}
loadArticles(){
articleService.fetchArticles().then((response) => {
this.setState({
articles: response.data
})
});
}
By inspecting with the React DevTool extension, I can see that the state has successfully changed and yet my component doesn't re-render. (If I don't check if articles are empty I get an error which says the .map() is undefined, which means that when the component was rendered it state.articles was filled with the initial response which is always empty)
render() {
let { articles } = this.state.articles;
return (
<div className="container">
<div className="row">
<div className="col-md-9">
{articles && articles.map(article => <Article key={article.id} article={article}/>)}
</div>
<div className="col-md-3">
widgets
</div>
</div>
</div>
)
}

let { articles } = this.state;
not let {articles} = this.state.articles

Related

Why isn't this React iteration of images working?

I'm learning React and calling Dog API. I got it to work for rendering an image, but when I tried to render multiple images, it doesn't work. For context, the API call link is https://dog.ceo/api/breeds/image/random/5 where "/5" specifies the number of images. I'm pretty sure that the state is being set because when I put console.log instead of setting the state, it's giving me the JSON with the urls to the images. I'm getting back an error message of "Cannot read property 'map' of undefined".
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount() {
fetch("https://dog.ceo/api/breeds/image/random/5")
.then(response => response.json())
.then(json => this.setState({data: json}));
}
render() {
return (
<div>
{this.state.data.message.map((item, id) => (
<img src={item} key={id} alt="dog" />
))}
</div>
)
}
}
export default App;
this.state.data.message doesn't exist when the component first loads. Instead, you've set this.state.data to an empty array, then later replace it with an object. It's best to keep the types consistent.
The simplest change is probably just to set up this.state.data.message to be an empty array:
constructor(props) {
super(props);
this.state = {
data: {
message: []
},
}
}
From there, taking note of the asynchronous nature of the AJAX operation, you might also consider implementing a "loading state" for when there are no images to display. (Perhaps even a meaningful empty state for when the operation has completed and there are still no images? An error state? etc.)
Check if the data has been manipulated or not. If not yet set the state by the API call then there is nothing this.state.data.message.
Note that, the ?. sign is called Optional Chaining. Optional chaining is used to check if an object has it's key or not in the deep level & also the Optional Chaining does not have the IE support.
render() {
return (
<div>
{this.state.data?.message?.length > 0 && this.state.data.message.map((item, id) => (
<img src={item} key={id} alt="dog" />
))}
</div>
);
}
It happened because when the page initially rendered, the data state is just an empty array. You have to add ? as an optional chaining which basically 'ignore' the error when you are accessing a property of something undefined.
Your code should be something like this
render() {
return (
<div>
{this.state.data.message?.map((item, id) => (
<img src={item} key={id} alt="dog" />
))}
</div>
)
}
So when the data state is empty it would not render anything.
Another way to do it is to check whether the state has content with
render() {
return (
<div>
{this.state.data.message &&
this.state.data.map((item, id) => (
<img src={item} key={id} alt="dog" />
))}
</div>
)
This code will check whether the data.message is truthy and will render the component, otherwise it will render nothing. More info here
}

How to render multiple items from an API call using React

I am using axios to get the data from an API. My component has an articles array in the state which is where the data from the API request will go.
I am still really new to React and trying to find information on displaying multiple articles on the page. That is what I am trying to figure out. Maybe I am using this.state wrong?
I've also tried fetch() then using a .then() to return the information. Neither way has helped me achieve what I am trying to display:
import React from "react";
import axios from "axios";
class Pages extends React.Component{
constructor(){
super();
this.state = {
articles : []
}
}
componentDidMount(){
this.getArticles().then(res =>
{
console.log(res);
this.setState({articles : res.data.articles});
});
}
getArticles(){
return axios.get('https://newsapi.org/v2/everything?domains=wsj.com,nytimes.com&apiKey=API_KEY');
};
render(){
return (
<div className="ui raised very padded text container segment">
<p>
Yo!
{this.state.articles[0] ? this.state.articles[0].author : ''}
</p>
</div>
);
}
}
export default Pages;
In the return section I want to display the article name, author, url link, and the content of the article.
If I understand correctly, you're wanting to render all articles that are available after your axios request completes.
To achieve that, consider using the map() method on the state.articles array. This allows you to "map" each item from the raw document JSON data to an <li> element containing the article information that you want to display to your users.
When rendering each <li>, you can access the content of the current article being mapped, to render that into the components overall rendered result:
render(){
return (
<div className="ui raised very padded text container segment">
<ul>
{ this.state.articles.map((article, index) => {
return (<li key={index}>
<h2>{ article.name }</h2>
<div>{ article.author }</div>
<p>{ article.content }</p>
<a href={article.url}>{ article.url }</a>
</li> )
})}
</ul>
<p>Yo!</p>
</div>
);
}
Something also to note is that, when rendering lists in React, you should include a unique key for each list item rendered (in the code above, the key is specified as the index of the article being mapped).
Update
To render the first article only (if present) while retaining your component's current state structure, you could slice() the articles array before performing the map(). This would cause a new array (with only the first article) to be passed to map() - the end result being that only the first article is rendered:
{ this.state.articles.slice(0,1).map((article, index) => {
/* Existing code from answer */
}}

Modifying State of Component - Error: Objects are not valid as a React child

I currently trying to get myself familiar with react and the state of a component.
I am trying to create a component, use the constructor to initialise the state, do a get request to the server to get an array of object and finally I am trying to update the state of the component accordingly. But I end up with the following error and I can't understand the problem:
Objects are not valid as a React child (found: object with keys {title, content}).
My code looks like this:
import React, { Component } from 'react';
import axios from "axios";
class Display extends Component {
constructor(){
super();
this.state = { posts: []}
}
componentDidMount(){
this.fetchData();
}
fetchData(){
const ROOT_URL = "http://localhost:5000";
axios.get(`${ROOT_URL}/api/post`)
.then(response => response.data.map(post => (
{
title: post.title,
content: post.content
}))
)
.then(
posts => {
this.setState({
posts
})
}
)
}
render(){
const { posts } = this.state;
return (
<div className="article">
List of Posts:
{this.state.posts}
</div>
)
}
}
export default Display;
Does anyone know what am I doing wrong?
Thanks
this.state.posts is an object like
{
title: post.title,
content: post.content
}
and hence you can't render it direct like
<div className="article">
List of Posts:
{this.state.posts}
</div>
You might as well render it like
<div className="article">
List of Posts:
{this.state.posts.map((post) => {
return <div>
<div>{post.title}</div>
<p>{post.content}</p>
</div>
})
</div>
above is assuming that content is also not an object or array. Check How to render an array of objects for more details
You would need to iterate over the posts array and return the html you want to display
Something like this
List of Posts:
{this.state.posts.map(post=>(
<div>
<span>{post.title}</span>
<p>{post.content}</p>
</div>
))}

React - Empty Array When Rendering JSON Data

Why is an empty array created when rendering this JSON array? See attached screenshot I assume the constructor is just initiating it with a null value and filling it at a later point.
New to Javascript + React and just want to make sure I am understanding what is happening. I will also accept critique on the garbage code that is below. Codepen link
class Jobs extends React.Component {
render() {
const jobs = this.props.jobs;
console.log(jobs);
const formattedJobs = jobs.map((job) =>
<ul key={job.id}>
<div class="company">{job.company_name}</div>
<div class="title">{job.title}</div>
</ul>
);
return(
<div>{formattedJobs}</div>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state={
jobs:[]
}
var myUrl = "https://codepen.io/jobs.json";
fetch(myUrl)
.then((response) => response.json())
.then((json) => this.setState({jobs: json.jobs}));
}
render() {
return (
<div className="app">
<div className="header">
<h1 id="header-title">Job Postings</h1>
</div>
<div className="content">
<Jobs jobs={this.state.jobs}/>
</div>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
You are getting the jobs from an ajax request wich is async. Thats why the initial value is an empty array.
App.render is executed before your ajax request is finished and thats why you are not givin any job to the Jobs component. Once the ajax is finished, the jobs array is filled with the results and sendeded to the Jobs component to render the results of that ajax request.
always use fetch statements in ComponentDidMount as it is called just after your component is rendered for the very first time
ComponentDidMount {
fetch(myUrl)
.then((response) => response.json())
.then((json) => this.setState({jobs: json.jobs}));
}
Jobs component
always be nice for default props - component can / will render before fetch returns response
replace class with className as first one is restricted word in JSX
ul can only contains li childs - not div
nice for use key property when you iterate over collection
App component
there is place for suppor unexpected response, you can set new state like error and support it
please consider with component is responsible to support errors and spinners- App or Jobs
Ract application
class Jobs extends React.Component {
render() {
const { jobs } = this.props;
const formattedJobs = jobs.map(job =>
<ul key={job.id}>
<li className="company">{job.company_name}</li>
<li className="title">{job.title}</li>
</ul>
);
return <div>{formattedJobs}</div>;
}
}
Jobs.defaultProps = {
jobs: []
};
class App extends React.Component {
constructor() {
super();
this.state = {
jobs: []
};
}
componentDidMount() {
fetch("https://codepen.io/jobs.json")
.then(response => response.json())
.then(response => {
if (!Array.isArray(response.jobs)) {
throw new Error(
`Expected response but got ${JSON.stringify(response.jobs)}`
);
} else {
return response.jobs;
}
})
.then(jobs => this.setState({ jobs }));
}
render() {
return (
<div className="app">
<div className="header">
<h1 id="header-title">Job Postings</h1>
</div>
<div className="content">
<Jobs jobs={this.state.jobs} />
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));

Cannot access key values in an object with React

Working on a project in React using a MongoDB database. I am trying to access key values in objects, but I get an error when I try to access them. I can access the objects themselves, but as soon as I use dot notation, React crashes and says TypeError: Cannot read property 'title' of undefined. The only instance I can get the data is when I console log in my fetchData() function before it goes through componentDidMount.
class TimelineContainer extends Component {
constructor(props){
super(props)
this.state = {
lifeEvents: [],
currentUser: auth.currentUser
}
}
componentDidMount(){
this.fetchData()
}
fetchData(){
LifeEventModel.all().then( (res) => {
this.setState ({
lifeEvents: res.lifeEvents,
uid: res.lifeEvents.uid,
currentUser: auth.currentUser
})
console.log(this.state.lifeEvents[0].title)
})
}
Link to database backend hosted on heroku. Link to my github repo.
render(){
console.log(this.state.lifeEvents[0].title)
console.log(this.state.lifeEvents);
return (
<div className='timelineContainer'>
{
(this.state.currentUser != null) ?
<div>
<CreateLifeEventForm
currentUser= {this.state.currentUser}
onCreateLifeEvent={this.createLifeEvent.bind(this)} />
<Timeline
currentUser= {this.state.currentUser}
lifeEvents={this.state.lifeEvents}
onDeleteLifeEvent={this.deleteLifeEvent.bind(this)}
onUpdateLifeEvent={this.updateLifeEvent.bind(this)}
/>
</div> :
<section className="col-md-4 col-sm-12 add-event">Log in to add a life event</section>
}
</div>
)
}
}
render method above. First console log throws the error. Second one does not.
it is possible that since you are not fetching your data asynchronously, the initial result this.state.lifeEvents will be null. so you have to first check for the null value until react updates the state and re-renders.
try this out:
renderView = () => {
return (this.state.lifeEvents === null)? <div>Loading...</div> :
(<div>
<CreateLifeEventForm
currentUser= {this.state.currentUser}
onCreateLifeEvent={this.createLifeEvent.bind(this)} />
<Timeline
currentUser= {this.state.currentUser}
lifeEvents={this.state.lifeEvents}
onDeleteLifeEvent={this.deleteLifeEvent.bind(this)}
onUpdateLifeEvent={this.updateLifeEvent.bind(this)}
/>
</div>)
};
render(){
return (
<div className='timelineContainer'>
{
(this.state.currentUser != null) ? this.renderView() :
<section className="col-md-4 col-sm-12 add-event">Log in to add a life event</section>
}
</div>
)
}

Categories

Resources