I have a components that aims to display data from API:
class Item extends Component {
constructor(props) {
super(props);
this.state = {
output: []
}
}
componentDidMount() {
fetch('http://localhost:3005/products/157963')
.then(response => response.json())
.then(data => this.setState({ output: data }));
}
render() {
console.log(this.state.output);
return (
<ItemPanel>
<ItemBox>
<BoxTitle>{this.state.output}</BoxTitle>
</ItemPanel>
);
}
export default Item;
console.log(this.state.output) returns proper data, while my component won't render, its throwing this error:
Objects are not valid as a React child (found: object with keys {id, general, brand, images}). If you meant to render a collection of children, use an array instead.
I guess the data from api is an object. I have tried using JSON.parse or toString() already :/
This is output from console:
It seems like you are displaying whole object in <BoxTitle>, Let's try to show brand here. I have update the code given in question.
Updated initial state output [] to {}
class Item extends Component {
constructor(props) {
super(props);
this.state = {
output: {}
}
}
componentDidMount() {
fetch('http://localhost:3005/products/157963')
.then(response => response.json())
.then(data => this.setState({ output: data }));
}
render() {
console.log(this.state.output);
const { brand = {name : ""} } = this.state.output // added default name until we get actual value
return (
<ItemPanel>
<ItemBox>
<BoxTitle>{brand.name}</BoxTitle>
</ItemPanel>
);
}
export default Item;
While your component fetches from your API it renders the element tree described in the render function.
Initial state for output is an empty array and that's a good start.
You should consider what to display to your application user when loading data from the API or if the network request fails.
I'm quite sure that you don't want to render the object returned upon a successful API call as-is.
That said, JSON.stringify function can be used for viewing what result is set in state upon a successful API call before picking what fields will be displayed, how and where they are displayed.
you can better use conditional rendering,
render() {
console.log(this.state.output);
return (
<ItemPanel>
<ItemBox>
{
if(this.state.output.brand.name !=== null) ?
<BoxTitle>{this.state.output.brand.name}</BoxTitle> :
<BoxTitle>Brand Name is Empty</BoxTitle>
}
</ItemPanel>
);
}
Related
In my React.js app, I am fetching a quote from API and storing it in the state object, When trying to access the properties of the object which is stored in state. Returning null.
Code:
import React, { Component } from "react";
export default class Quotes extends Component {
constructor(props) {
super(props);
this.state = {
quote: null,
};
}
componentDidMount() {
fetch("https://quote-garden.herokuapp.com/api/v2/quotes/random")
.then((response) => response.json())
.then((quote) => this.setState({ quote: quote.quote }))
.catch((error) => console.log(error));
}
render() {
console.log(this.state.quote.quoteText); //This is returning null
console.log(this.state.quote); //This is working fine.
return (
<div>
<p>// Author of Quote</p>
</div>
);
}
}
I am new to React.js, I spining my head for this problem around 2 hourse. I didn't get any solution on web
output showing quote object
When I try to console.log(this.state.quote) it prints out this output, well it is fine.
and when I try console.log(this.state.quote.quoteText) It will return can not read property
output showing can not find property
You must note the you are trying to fetch data asynchronously and also you fetch data in componentDidMount which is run after render, so there will be an initial rendering cycle wherein you will not have the data available to you and this.state.quote will be null
The best way to handle such scenarios is to maintain a loading state and do all processing only after the data is available
import React, { Component } from "react";
export default class Quotes extends Component {
constructor(props) {
super(props);
this.state = {
quote: null,
isLoading: true,
};
}
componentDidMount() {
fetch("https://quote-garden.herokuapp.com/api/v2/quotes/random")
.then((response) => response.json())
.then((quote) => this.setState({ quote: quote.quote, isLoading: false }))
.catch((error) => console.log(error));
}
render() {
if(this.state.isLoading) {
return <div>Loading...</div>
}
console.log(this.state.quote.quoteText); //This is returning proper value
console.log(this.state.quote);
return (
<div>
<p>// Author of Quote</p>
</div>
);
}
}
When you are accessing the quoteText without checking whether the key in the object is present or not it throws an error -
render(){
if(this.state.quote && this.state.quote.hasOwnProperty('quoteText')){
console.log(this.state.quote.quoteText);
}
}
I'm learning react and it's great, but i've ran into an issue and i'm not sure what the best practice is to solve it.
I'm fetching data from an API in my componentDidMount(), then i'm setting some states with SetState().
Now the problem is that because the first render happens before my states have been set, im sending the initial state values into my components. Right now i'm setting them to empty arrays or empty Objects ({ type: Object, default: () => ({}) }).
Then i'm using ternary operator to check the .length or if the property has a value.
Is this the best practice or is there some other way that i'm unaware of?
I would love to get some help with this, so that i do the basics correctly right from the start.
Thanks!
I think the best practice is to tell the user that your data is still loading, then populate the fields with the real data. This approach has been advocated in various blog-posts. Robin Wieruch has a great write up on how to fetch data, with a specific example on how to handle loading data and errors and I will go through his example here. This approach is generally done in two parts.
Create an isLoading variable. This is a bolean. We initially set it to false, because nothing is loading, then set it to true when we try to fetch the data, and then back to false once the data is loaded.
We have to tell React what to render given the two isLoading states.
1. Setting the isLoading variable
Since you did not provide any code, I'll just follow Wieruch's example.
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataFromApi: null,
};
}
componentDidMount() {
fetch('https://api.mydomain.com')
.then(response => response.json())
.then(data => this.setState({ dataFromApi: data.dataFromApi }));
}
...
}
export default App;
Here we are using the browser's native fetch() api to get the data when the component mounts via the use of componentDidMount(). This should be quite similar to what you are doing now. Given that the fetch() method is asynchronous, the rest of the page will render and the state will be up dated once the data is received.
In order to tell the user that we are waiting for data to load, we simply add isLoading to our state. so the state becomes:
this.state = {
dataFromApi: null,
isLoading: false,
};
The state for isLoading is initially false because we haven't called fetch() yet. Right before we call fetch() inside componentDidMount() we set the state of isLoading to true, as such:
this.setState({ isLoading: true });
We then need to add a then() method to our fetch() Promise to set the state of isLoading to false, once the data has finished loading.
.then(data => this.setState({ dataFromAPi: data.dataFromApi, isLoading: false }));
The final code looks like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataFromApi: [],
isLoading: false,
};
}
componentDidMount() {
this.setState({ isLoading: true });
fetch('https://api.mydomain.com')
.then(response => response.json())
.then(data => this.setState({ dataFromApi: data.dataFromApi, isLoading: false }));
}
...
}
export default App;
2. Conditional Rendering
React allows for conditional rendering. We can use a simple if statement in our render() method to render the component based on the state of isLoading.
class App extends Component {
...
render() {
const { hits, isLoading } = this.state;
if (isLoading) {
return <p>Loading ...</p>;
}
return (
<ul>
{dataFromApi.map(data =>
<li key={data.objectID}>
<a href={data.url}>{data.title}</a>
</li>
)}
</ul>
);
}
}
Hope this helps.
It Depends.
suppose you are fetching books data from server.
here is how to do that.
state = {
books: null,
}
if, your backend api is correctly setup.
You will get either empty array for no books or array with some length
componentDidMount(){
getBooksFromServer().then(res => {
this.setState({
books: res.data
})
})
}
Now In Your render method
render() {
const { books } = this.state;
let renderData;
if(!books) {
renderData = <Spinner />
} else
if(books.length === 0) {
renderData = <EmptyScreen />
}
else {
renderData = <Books data = { books } />
}
return renderData;
}
If you are using offline data persistence In that case initially you won't have empty array.So This way of handling won't work.
To show the spinner you have to keep a variable loader in state.
and set it true before calling api and make it false when promise resolves or rejects.
finally read upon to state.
const {loader} = this.state;
if(loader) {
renderData = <Spinner />
}
I set initial state in constructor. You can of course set initial state of component as static value - empty array or object. I think better way is to set it using props. Therefore you can use you component like so <App items={[1,2,3]} /> or <App /> (which takes value of items from defaultProps object because you not pass it as prop).
Example:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: [], // or items: {...props.items}
};
}
async componentDidMount() {
const res = await this.props.getItems();
this.setState({items: res.data.items})
}
render() {
return <div></div>
}
};
App.defaultProps = {
items: []
}
My application makes an API call and turns each element of a JSON array into a React Component.
I made an array of these child components, but they do not render. How do I force them to render? Should I use the React.create() and then call render() on each?
What is the proper vanilla React design pattern for this?
var apiPosts = [];
class PostsItsContainer extends Component {
constructor(props){
super(props);
this.state = {}
}
componentDidMount(){
let uri = "some_API_endpoint" ;
if(uri){
fetch(uri)
.then(data => data.json())
.then(posts => {
posts.data.children.forEach(post => {
let imgUrl = post.data.hasOwnProperty('preview') ? post.data.preview.images[0].source.url : null;
let postData = [post.data.title, imgUrl, post.data.link];
apiPosts.push(<PostIt link={post.data.url} image={imgUrl} title={post.data.title} />);
});
}).catch(err => console.log(err)) }
}
render() {
return (
<div className="PostsItsContainer">
{apiPosts}
</div>
);
}
}
EDIT:
I changed my title cause it was pretty generic. And really I was asking why my method was bad design practice and wouldn't give me proper results.
#Jayce444 told me why and #Supra28 gave a good answer. I'm posting #Jayce444's comment here for it to be easily read:
It's perfectly possible to store a component in a variable or array and then use that. But the store/props should be reserved for the bare bones data needed to render stuff, not the entire pre-made component. There's s few reasons, two being: firstly you'll bloat the state/props doing that, and secondly you're combining the logic and view functionalities. The data needed to render a component and the actual way it's rendered should be loosely coupled, makes your components easier to maintain, modify and understand. It's like separating HTML and CSS into separate files, it's easier :)
So what we do here is :
1) Set the loading state to true initially
2) When we get the data from the api we want our component to rerender to display the new data, so we keep the data in the state.
3) Inside of the render function we return a Loding indicator if our loading indicator is true or return the array of posts (map returns an array) wrapped with a div.
class PostsItsContainer extends Component {
constructor(props) {
super(props)
this.state = { apiData: [], loadingPosts: true } // Loading state to know that we are loading the data from the api and do not have it yet so we can display a loading indicator and don't break our code
}
componentDidMount() {
let uri = "some_API_endpoint"
if (uri) {
fetch(uri)
.then(data => data.json())
.then(posts => {
this.setState({ apiData: posts.data.children, loadingPosts: false }) //Now we save all the data we get to the state and set loading to false; This will also cause the render function to fire again so we can display the new data
})
.catch(err => console.log(err))
}
}
render() {
if (this.state.loadingPosts) return <div>Loading......</div> //If we haven't recieved the data yet we display loading indicator
return (
<div className="PostsItsContainer">
{this.state.postData.map((post, i) => (
<PostIt
key={i} //You'll need a key prop to tell react this is a unique component use post.id if you have one
link={post.data.url}
image={
post.data.preview ? post.data.preview.images[0].source.url : null
}
title={post.data.title}
/>
))}
</div>
)
}
}
My React Component has the following render
componentWillMount () {
var url = 'https://gist.githubusercontent.com/hart88/198f29ec5114a3ec3460/raw'
Request.get(url)
.then(data => {
this.setState({cakes: data.text});
})
}
render() {
return(
<div>
{this.state.cakes} //prints this ok
{
this.state.cakes.map(cake =>{ // error here
return <p>{cake.title}</p>;
})
}
</div>
);
}
i am trying to loop through this.state.cakes which is an array of objects.
What am i doing wrong here ?
Update - an abbreviated example of this.state.cakes:
[
{
"title": "Lemon cheesecake",
"desc": "A cheesecake made of lemon",
"image":"https://s3-eu-west-1.amazonaws.com/s3.mediafileserver.co.uk/carnation/WebFiles/RecipeImages/lemoncheesecake_lg.jpg"
},
{
"title":"Banana cake",
"desc":"Donkey kongs favourite",
"image":"http://ukcdn.ar-cdn.com/recipes/xlarge/ff22df7f-dbcd-4a09-81f7-9c1d8395d936.jpg"
}
]
Thanks
If the state is set as the resutl of a fetch you might not be able to access the data immediately due to the async operation. You can catch this by inspecting the state and if it has no length return a message or a spinner component to indicate the data's on its way.
Once state.cakes is updated with the data from the fetch operation the component will re-render.
constructor(props) {
super(props);
this.state = { cakes: [] };
}
componentDidMount() {
fetch('/cakes')
.then(res => res.json())
.then(cakes => this.setState({ cakes }));
}
render() {
if (!this.state.cakes.length) return <Spinner />
return (
<div>
{this.state.cakes.map(cake => {
return <p>{cake.title}</p>;
})};
</div>
)
}
As the others have mentioned it's also good practice to add keys to your iterated elements.
Here:
{this.state.cakes.map((cake, i) => <p key={i}>{cake.title}</p>;)}
Do not forget to add the key attribute.
Ps: It would be better to use an unique Id instead of the array index. SO if you have an id for each array item, better write:
{this.state.cakes.map(cake => <p key={cake.id}>{cake.title}</p>;)}
I believe that you've used curly braces (understandably) where React actually requires parentheses. Since you're getting the data from a fetch, be sure to set your constructor with a preliminary cakes object as well. Try this:
constructor(props) {
super(props)
this.state = {
cakes: []
}
}
render() {
if (this.state.cakes.length > 0){
return(
<div>
{
this.state.cakes.map(cake => (
return <p>{cake.title}</p>;
))
}
</div>
);
}
return null
}
The issue is that the component is rendering and you're telling it to do something with an array called this.state.cakes, but this.state.cakes hasn't been defined yet because the fetch hasn't returned yet. Setting your constructor like this passes an empty array to the render so it doesn't freak out, and then when your data loads and your state updates, it will re-render with your data.
The reason {this.state.cakes} was, on its own, rendering just fine is because for the first split second of the component's existence, that value was undefined, which means that React basically just ignored it - once the data loaded, it rendered. However, the map method failed because you cannot pass an undefined array into map.
And as Ha Ja suggested, you should probably add a key attribute to the <p> elements.
You missed brackets inside of your map
{this.state.cakes.map(cake =>{ // errors here
return <p> {cake.title} </p>;
})}
I have a React.js component which pulls its initial state data from an API call in componentDidMount(). The data is an array of objects.
I am able to view the array, and individual elements using JSON.stringify (for debugging), but when I try to access a property in an element, I get an error which seems to imply that the element is undefined, despite having checked that it is not.
Code:
class TubeStatus extends Component {
constructor(props) {
super(props);
this.state = { 'tubedata' : [] };
};
componentWillMount() {
let component = this;
axios.get('https://api.tfl.gov.uk/line/mode/tube/status')
.then( function(response) {
component.setState({ 'tubedata' : response.data });
})
.catch( function(error) {
console.log(JSON.stringify(error, null, 2));
});
};
render() {
return (
<div><pre>{this.state.tubedata[0].id}</pre></div>
);
}
}
Error:
Uncaught TypeError: Cannot read property 'id' of undefined
If I use JSON.stringify() to display this.state.tubedata, all the data is there.
In my admittedly limited knowledge of React.js, I suspect this is because React.js is trying to access the .id property before componentDidMount() fires loading the initial state data, thus returning undefined, but I could be completely wrong.
Anyone able to point me in the right direction?
As you are fetching data from API call, on initial rendering data is not available hence you are getting error.
this.state.tubedata.length>0// check if tubedata is available
So,
render() {
return (
<div><pre>{this.state.tubedata.length>0?this.state.tubedata[0].id:null}</pre></div>
);
}
this is because you are having a async request and since the state array is initially empty this.state.tubedata[0] is initially undefined
Keep a check before using id like
<div><pre>{this.state.tubedata.length > 0 && this.state.tubedata[0].id}</pre></div>
class TubeStatus extends React.Component {
constructor(props) {
super(props);
this.state = { 'tubedata' : [] };
};
componentWillMount() {
let component = this;
};
render() {
return (
<div>Hello <pre>{this.state.tubedata.length > 0 && this.state.tubedata[0].id}</pre></div>
);
}
}
ReactDOM.render(<TubeStatus/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>