Loop through objects React - javascript

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>;
})}

Related

"TypeError: Cannot read property 'map' of undefined" but can console log result completely fine?

I have two pieces of state currently. One is an empty array called "subproducts", the other is called "additionalprod".
subproducts is an array which pulls information from my backend database using Axios. It is mounted inside of a componentDidMount for the API call.
additionalprod(uct) is a piece of state that gets its value from a prop from a parent, it is a number, in this case for my testing purposes, the number is 5. Number 5 links to a small array containing 3 properties. Product, img_url and id.
When I console.log the exact code
console.log(this.state.subproducts[5])
I get the response I want, that specific array, so I am 100% targeting it correctly. It currently has 2 arrays inside of it with their own props.
However, when I try and map through that state, using the code
{this.state.subproducts[5].map((product) => {
<h1>{product.img_url}</h1>
})}
I get the error
TypeError: Cannot read property 'map' of undefined
Anybody know whats wrong?
Code in question:
import React from "react"
import axios from "axios"
class SubProducts extends React.Component {
constructor(props) {
super(props)
this.state = {
subproducts: [],
stage: "3",
selected: this.props.selected,
additionalprod: this.props.additionalprod,
}
}
componentDidMount() {
axios.get("http://localhost/api/subproducts.php").then((res) => {
this.setState({ subproducts: res.data })
console.log(this.state.subproducts[5])
})
}
render() {
return (
<div>
{this.state.subproducts[this.state.additionalprod].map((product) => {
;<h1>{product.img_url}</h1>
})}
</div>
)
}
}
export default SubProducts
EDIT:
After using Sean Rileys fix, I am trying to console log {product} by itself and now I am being hit with this in the console, still can't get it to output though by putting {product.img_url}, there is an error, 'Failed to compile'
On the initial render, this.state.subproducts is empty so it can't run the map function on the array. It is only after the API call that the array is filled with data.
This is a very common React issue which was a very simple solution of checking that the array isn't empty before running the method on it.
{this.state.subproducts && this.state.subproducts[this.state.additionalprod].map((product) => {
<h1>{product.img_url}</h1>
})}
this.state.subproducts &&... is just checking if the array returns truthy which it will if it isn't empty and then, if so, it will run the next bit of code.
Another option is "optional chaining" which pretty much does the same thing.
{this.state.subproducts[this.state.additionalprod]?.map((product) => {
<h1>{product.img_url}</h1>
})}
Notice the question mark right before map ?.map. By adding the question mark after the array that we're calling the function on, it will only run if the array is defined otherwise it will return undefined but you won't get an error.
You have to wait for a response before render, just add checking on length for your array:
componentDidMount() {
this.setState({additionalprod: this.props.additionalprod}, () => {
axios.get("http://localhost/api/subproducts.php").then((res) => {
this.setState({ subproducts: res.data })
console.log(this.state.subproducts[5])
});
}
}
render() {
return (
<div>
{this.state.subproducts.length > 0 ?
this.state.subproducts[this.state.additionalprod].map((product) =>
<h1>{product.img_url}</h1>
}) : null
</div>
)
}

Data from API rendering as empty (array/object issue)

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>
);
}

React : Invariant Violation: Objects are not valid as a React child

I am working on ReactJS Search filter , Currently I am facing a problem when I enter match input application is crashed and give this error Objects are not valid as a React child (found: object with keys {id, companyName, account, venueCode, openDate, website, primaryPhone, emailAddress, description, firstName, lastName, active, title, department, officePhone, mobilePhone, tenantId, hidden, deleted, parentId}). If you meant to render a collection of children, use an array instead. Somebody please help me how to solve this problem . I am beginner and don't have much knowledge to resolve this problem . First time application is rendering successfully when I enter some match input it give me an error .
Code
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
Item: 5,
skip: 0
}
this.handleClick = this.handleClick.bind(this);
}
urlParams() {
return `http://localhost:3001/meetups?filter[limit]=${(this.state.Item)}&&filter[skip]=${this.state.skip}`
}
handleClick() {
this.setState({skip: this.state.skip + 1})
}
render() {
return (
<div>
<a href={this.urlParams()}>Example link</a>
<pre>{this.urlParams()}</pre>
<button onClick={this.handleClick}>Change link</button>
</div>
)
}
}
ReactDOM.render(<Example/>, document.querySelector('div#my-example' ))
This happens when you try to render an object instead of JSX. Making my best educated guess, I think this line is the problem:
let filtered=this.state.data.filter((item)=>{
return item.companyName.indexOf(keyword) > -1
});
Filtered appears to be an array of objects, not JSX, so then in the render method:
{this.state.filtered.length === 0 ? dataRender : this.state.filtered}
potentially tries to render filtered objects, not JSX.
To fix this, try adding this:
const filterRender=this.state.filtered.map((dataItem)=>(
<Table.Row key={dataItem.id}>
<Table.Cell>{dataItem.companyName}</Table.Cell>
<Table.Cell>{dataItem.primaryPhone}</Table.Cell>
<Table.Cell>{dataItem.emailAddress}</Table.Cell>
<Table.Cell>{dataItem.venueCode}</Table.Cell>
<Table.Cell>{dataItem.account}</Table.Cell>
<Table.Cell>{dataItem.openDate}</Table.Cell>
<Table.Cell>{dataItem.website}</Table.Cell>
<Table.Cell>{dataItem.description}</Table.Cell>
</Table.Row>
))
and changing this to:
{this.state.filtered.length === 0 ? dataRender : filterRender}
As #jsdeveloper pointed out below, it would be a good idea to make a renderRow method to handle this.
The only thing I would add to asleepace answer would be that you should create a function to map to a dataitem:
getDataItems(data) {
return data.map((dataItem)=>(
<Table.Row key={dataItem.id}>
<Table.Cell>{dataItem.companyName}</Table.Cell>
<Table.Cell>{dataItem.primaryPhone}</Table.Cell>
<Table.Cell>{dataItem.emailAddress}</Table.Cell>
<Table.Cell>{dataItem.venueCode}</Table.Cell>
<Table.Cell>{dataItem.account}</Table.Cell>
<Table.Cell>{dataItem.openDate}</Table.Cell>
<Table.Cell>{dataItem.website}</Table.Cell>
<Table.Cell>{dataItem.description}</Table.Cell>
</Table.Row>
))
}
render() {
const filteredItems = getDataItems(this.state.filtered)
const dataItems = getDataItems(this.state.data)
...
You should add a state prop that handles if the Component is ready to render or you have to render a loader.
This will solve the issue that if your property this.state.data ist already populated by your getDataMethod.
So you should try adding a inital state proeprty like:
class Organization extends Component {
constructor(props){
super(props);
this.state={
Item : 5,
skip:0,
isReady: false,
data : [],
filtered:[]
}
this.getData=this.getData.bind(this);
this.btnClick=this.btnClick.bind(this);
this.prevButton=this.prevButton.bind(this);
}
and you should handle the isReady State in your getData like :
getData(){
const {Item,skip}=this.state;
axios.get(`http://localhost:8001/parties?filter[limit]=${Item}&&filter[skip]=${skip}`)
.then(response=>{
console.log(response.data);
this.setState({
isReady: true,
data:response.data
})
})
}
And add a condition in your render method:
render() {
if( !this.state.isReady ){
return <div>Loading...</div>
}
// return your JSX
return ....
}
I cannot test this to be certain but I expect that that dataRender const is returning a series of rows without a containing parent. The correct way to do this would be to create a <TableRow /> component that uses the map function inside the render function in your jsx like so:
...
<Table.Body>
{this.state.filtered.length === 0 ?
this.state.data.map((dataItem)=>(<TableRow key={dataItem.id} item={dataItem}/>)
: this.state.filtered}
</Table.Body>
...
I had this issue and it was a really simple solution. I had an object nested within an object, then I tried displaying that object within JSX, rather than a property of that object. This error was showing at the line where that object was set to the state, not where the object was called from the state within JSX.
Object in the state:
test: { status: { id: 1, text: "value" } }
JSX (wrong)
<div>{this.state.test.status}</div>
JSX (right)
<div>{this.state.test.status.text}</div>

Adding items to an array in javascript

Admit it. Being new to JavaScript in 2018 is difficult. Coming from languages like C#, Java and Typescript(yeah subset of js..) where type safety is key, Javascript just keep f***** me over. I struggle with something simple like updating an array..
So I have this React component, where the state is defined like this:
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
show: false,
shoes: []
};
}
....
...
}
The shoes is an array of undefined(?)
This array is passed to a stateless component which looks like this
const Shoelist = props => {
return (
<Wrapper>
{props.shoes.map((shoe, i) => (
<div key={i}>
<Shoe shoe={shoe} />
<Separator />
</div>
))}
</Wrapper>
);
};
I in my Form component, I have a method which is supposed to react(doh) on onClick methods. In this method I get a parameter with a new shoe to add in this list. This is very it stops for me in javascript - something which is faaaaairly easy in all other languages that we've being using for the past years..
I've tried several ways:
1#
addShoe(shoe) {
this.setState(state => {
const list = state.shoes.push(shoe);
return {
list
};
});
}
This results in an error: Uncaught TypeError: Cannot read property 'push' of undefined Do I need to define shoes as an Array? I thought the [] was enough
2#
I googled, I do that alot. I found one blog post saying something about react-addons-update. I installed this by running yarn add and code looks like this:
addShoe(shoe) {
this.setState(update(this.state, { shoes: { $push: [shoe] } }));
}
which results in Uncaught Error: update(): expected target of $push to be an array; got undefined.
Help! How difficult can this be?
EDIT
I pass this method into another component like this:
<ShoeModal onClose={this.addShoe} />
in the ShoeModal component this is bound to a onClick method:
<FinishModalButton
onClick={this.props.onClose.bind(this, this.state.shoe)}>
....
</FinishModalButton>
ShoeModal.propTypes = {
onClose: PropTypes.func.isRequired
};
You can do it this way:
this.setState({
shoes: [...this.state.shoes, newShoe]
})
... adds all elements from this.state.shoes
With your updates we can see that the issue is the way the addShoe callback is passed. It's being invoked as a function instead of a method of an object, so it loses context.
Change it to:
<ShoeModal onClose={this.addShoe.bind(this)} />
or
<ShoeModal onClose={shoe => this.addShoe(shoe)} />
In addition, .push returns the count of the array, so the following line won't give you what you expect:
const list = state.shoes.push(shoe);
See #merko's answer for a solution.
Firstly, your addShoe method is not an arrow function.
Using arrow functions because the context this is of the component.
Second, you are returning the object {list}. This sets the variable list in state.
Also push to the new list variable instead of mutating state.
Change your function to
addShoe = (shoe) => {
this.setState(state => {
let list = state.shoes;
list.push(shoe);
return {
shoes : list
};
});
}

Having trouble with React rendering a series of images from a map function

I'm trying to design a component where the user can click a button which will trigger a giphy API call that will eventually render a series of gif images.
So far I'm able to successfully get everything done except the actual image rendering. Here's my code so far:
retrieveURLs() {
*bunch of stuff to make API call and return an array of URLs related
to the category of the button the user pushes*
}
renderGifs() {
this.retrieveURLs().then(function(results) {
console.log(results); //logs array of URLs
return results.map(function(url, index) {
console.log(url); //logs each url
return (<img key={index} src={url} alt="" />)
}, this);
});
}
render() {
return(
<div id="gif-div">
{this.renderGifs()}
</div>
)
}
Nothing gets rendered despite each console.log() event indicating that the URL(s) are at least being passed properly.
I do something similar for the parent component to render the buttons from an array of categories that looks like this:
btnArray = [*bunch of categories here*];
renderButtons() {
return btnArray.map(function(item, i) {
let btnID = btnArray[i].replace(/\s+/g, "+");
return <button type='button' className="btn btn-info" onClick={this.handleCategorySelect} key={i} id={btnID} value={btnID}>{btnArray[i]}</button>
}, this)
}
The buttons are rendered properly, but my images are not. Neither the renderbuttons nor the rendergifs metohds alter the state. Honestly I can't see a meaningful difference between the two, so I'd like to have some help figuring out why one works but the other doesn't.
This is the nature of asynchronous functions; you can't return a value from within a callback to the original call site. If you were to write:
const res = this.retrieveURLs().then(function(results) {
return results;
});
you'd only be changing the resolution value of the promise. res won't be assigned the value of results, but rather it will be assigned the promise created by this.retrieveURLs(), and the only way to retrieve the value of a resolved promise is by attaching a .then callback.
What you could do is this:
this.retrieveURLs().then(results => {
this.setState({ images: results });
});
Now your internal state will be updated asynchronously, and your component will be told to re-render with the new data, which you can use in your render function by accessing the state.
Note: I'm using an arrow function to create the callback in the above example, because otherwise this won't be bound to the right context. Alternatively, you could do the old that = this trick, or use Function#bind.
The problem lies with the rendering function for the images and the way React does diffing between the previous and current state. Since the fetch is asynchronous and the render is not, when the fetch is completed React doesn't know that it needs to re-render you component. There are ways to force a re-render in this case, but a better solution is to use the functionality that's already part of the shouldUpdate check.
Your implementation might change to look something like the following:
class Images extends Component {
state = {
images: [],
error: null
}
componentDidMount() {
return this.retrieveImages()
.then(images => this.setState({images}))
.catch(error => this.setState({error}))
}
... rest of your class definition
render () {
return (
<div>
{this.state.images.map(image => <img src={image.url} />)}
</div>
)
}
}
I would also have some handling for bad results, missing key / values, etc. I hope that works for you, if not let me know! :)
first of all you forgot the return statement:
renderGifs() {
return this.retrieveURLs().then(function(results) {
...
}
but this won't solve anything as it is returning a Promise.
You need to save request results in the state and then map it:
constructor(props){
super(props);
this.state = { images: [] };
}
componentDidMount() {
this.renderGifs();
}
renderGifs() {
this.retrieveURLs().then(function(results) {
console.log(results); //logs array of URLs
this.stateState({ images: results });
});
}
render() {
return(
<div id="gif-div">
{
this.state.images.map((url, index) => (<img key={index} src={url} alt="" />);
}
</div>
)
}

Categories