I am using React as a beginner, and I looked up the error I was getting and most people say you cannot use map with an object, it must be an array. But I am getting back the following data for my props.items when put in console.log:
[{
ItemKey: 82521,
ISBN: "158344422X",
Title: "Butterflies And Moths",
Publisher: "Benchmark Education",
Illustrator: " "
}, {
ItemKey: 85938,
ISBN: "9780452274426",
Title: "In the Time of the Butterflies",
Publisher: "Penguin Plume",
Illustrator: " "
}]
So it is returning an array. But I still get the map is not a function error. Here is my code:
function ItemGrid (props){
return (
<ul className='items-list'>
{props.items.map(function(item, index){
<li key={item.Title} className="items-list-item">
</li>
})}
</ul>
)
}
Does anyone see anything unusual about the code I wrote or the object getting returned?
EDIT: Here is how I am getting the data:
module.exports = {
fetchItems: function(){
var encodedURI = window.encodeURI('http://localhost:8081/items?Title=butterflies', { crossdomain: true });
return axios.get(encodedURI).then(function(response){
return response.data;
});
}
}
And I am passing data to ItemGrid through:
componentDidMount(){
api.fetchItems().then(function(items){
this.setState(function(){
return {
items: items
}
})
}.bind(this));
}
render() {
return (<div>
<ItemGrid items={this.state.items} />
</div>);
}
I am also having trouble getting the li items to show up once the data is present:
<ul className='items-list'>
{items && items.map(function (item, index) {
console.log(item);
<li key={index} className='items-list-item'>
item.Title
</li>
})}
</ul>
The render function can and will be called several times, often without any data the first time through.
So you need to check if your data is present before you try and iterate over it with the map() function.
if props.items is not present you can return something simple like null or even "Loading", and the next time around (when the data is there) it will work as expected.
So your new code could be this (only rendering if props.items is defined):
function ItemGrid (props){
return (
<ul className='items-list'>
{props.items && props.items.map(function(item, index){
<li key={item.Title} className="items-list-item">
</li>
})}
</ul>
)
}
The correct solution depends on how the data is being retrieved. If it's a simple JS array within your script, the data should be available when the component is mounted.
var data = [{
ItemKey: 82521,
ISBN: "158344422X",
Title: "Butterflies And Moths",
Publisher: "Benchmark Education",
Illustrator: " "
}, {
ItemKey: 85938,
ISBN: "9780452274426",
Title: "In the Time of the Butterflies",
Publisher: "Penguin Plume",
Illustrator: " "
}];
function ItemGrid(props) {
var items = props.items;
return (
<ul className='items-list'> {
items.map(function(item, index) {
return (<li key={item.ItemKey} className="items-list-item">{item.Title}</li>)
})
} </ul>
)
}
ReactDOM.render(<ItemGrid items={data} />, 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>
On the other hand, if you're retrieving the data from an asynchronous request it may not be available when the component is mounted and you'll need to handle the case when props.items is undefined/empty/null. In the example below we wrap the <ItemsGrid/> component in a <Parent/> component which passes the items as a prop to <ItemsGrid/>. Initially the items is null, and after 3 seconds it changes to an array. This simulates an asynchronous data source.
While items is null we return a list showing Loading..., and when items changes to a valid value we display the items.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null
};
window.setTimeout(function(){
this.setState({
data: [{
ItemKey: 82521,
ISBN: "158344422X",
Title: "Butterflies And Moths",
Publisher: "Benchmark Education",
Illustrator: " "
}, {
ItemKey: 85938,
ISBN: "9780452274426",
Title: "In the Time of the Butterflies",
Publisher: "Penguin Plume",
Illustrator: " "
}]
})
}.bind(this), 3000);
}
render() {
return (
<ItemGrid items={this.state.data} />
)
}
}
function ItemGrid(props) {
var items = props.items;
if (!items) {
return (
<ul>
<li>Loading...</li>
</ul>
);
}
return (
<ul className='items-list'> {
items && items.map(function(item, index) {
return (<li key={item.ItemKey} className="items-list-item">{item.Title}</li>)
})
} </ul>
)
}
ReactDOM.render(<Parent />, 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>
If you are using redux, this is a very easy fix, but if you aren't:
What is happening is when the component mounts, it immediately searches for props.items, but it is null. You have an asynchronous event that eventually gets the data, but it's not doing it fast enough.
The easiest way to fix this is to control what your .map is accepting by using a conditional statement before your component attempts to render content.
function ItemGrid (props){
const items = props.items ? props.items : []
return (
<ul className='items-list'>
{items.map(function(item, index){
<li key={item.Title} className="items-list-item">
</li>
})}
</ul>
)
}
Check to see if props.items is null or not. If it is, render an empty array so it doesn't throw an error. If it does exist, it'll use props.items.
Please keep in mind that this will only work if a rerender attempt is made after you receive the array of data. I would personally uses promises and state to control rendering, because that will work in any situation. It's a much more involved solution, but it's something you should know like the back of your hand as a React developer. Keep us updated.
Related
Hmm, I don't see my omission, but I get a blank page with a console error saying:
Users.js:9 Uncaught TypeError: Cannot read property 'filter' of undefined
at Users.render (Users.js:9)
Apparently I'm using 'filter()' improperly. I looked around but didn't find anything 'React' related. Can Anyone help? Here are the files:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Users from './Users';
ReactDOM.render(
<Users list={[
{ name: 'Tyler', friend: true },
{ name: 'Ryan', friend: true },
{ name: 'Michael', friend: false },
{ name: 'Mikenzie', friend: false },
{ name: 'Jessica', friend: true },
{ name: 'Dan', friend: false }
]}
/>,
document.getElementById('root')
);
Users.js
import React from 'react';
class Users extends React.Component {
render() {
return (
<div>
<h1>Friends:</h1>
<ul>
{this.props.list.friend.filter(function (friend) {
return <li>{friend[0] === 'true'}</li>
})}
</ul>
<hr />
<h1>Non Friends:</h1>
<ul>
{this.props.list.friend.filter(function (nonFriend) {
return <li>{nonFriend[0] === 'false'}</li>
})}
</ul>
</div>
);
}
}
export default Users;
Okay, looks like "Users.js" should be:
import React from 'react';
class Users extends React.Component {
render() {
let friends = this.props.list.filter( function (user) {
return user.friend === true
});
let nonFriends = this.props.list.filter( function (user) {
return user.friend !== true
});
return (
<div>
<h1>Friends:</h1>
<ul>
{friends.map(function (user) {
return <li key={user.name}>{user.name}</li>
})}
</ul>
<h1>Non Friends:</h1>
<ul>
{nonFriends.map(function (user) {
return <li key={user.name}>{user.name}</li>
})}
</ul>
</div>
);
}
}
export default Users;
Or even this:
import React from 'react';
class Users extends React.Component {
render() {
return (
<div>
<h1>Friends:</h1>
<ul>
{this.props.list.filter(function (user) { // filter first for friends
return user.friend === true // returns a new array
}).map(function (user) { // map the new array to list items
return <li key={user.name}>{user.name}</li> // don't forget unique key for each item
})}
</ul>
<hr />
<h1>Non Friends:</h1>
<ul>
{this.props.list.filter(function (user) { // filter first for non-friends
return user.friend !== true // returns a new array
}).map(function (user) { //map the new array to list items
return <li key={user.name}>{user.name}</li> // don't forget unique key for each item
})}
</ul>
</div>
);
}
}
export default Users;
You are calling .friend on the list itself when that's a property of each object in your array. You are also using .filter, but I don't think you're using it correctly here. .filter will return an array with certain elements where the function passed in returns a truthy value. Here's how you could do it with .filter:
var nonFriends = this.props.list.filter(function (user) {
return !user.friend;
});
var friends = this.props.list.filter(function (user) {
return user.friend;
});
return (
<div>
<h1>Friends:</h1>
<ul>{ friends }</ul>
<h1>Non Friends:</h1>
<ul>{ nonFriends }</ul>
</div>
);
You could also do a .forEach for 1 pass through the array if the array is large:
var nonFriends = [], friends = [];
this.props.list.forEach(function (user) {
if (user.friend) {
friends.push(user);
} else {
nonFriends.push(user);
}
});
// now render friends and nonFriends
I would do something like this instead which is a little more straightforward
{this.props.list.map(function (person, i) {
{return person.friend
?(<li key={i}>{person.name}</li>)
: null
}
})}
You are iterating over the list itself, not an item in the list which is why this.props.list.friend.filter didn't work.
I would use map because you are not actually filtering the lists in this case. If you wanted to you could filter the list beforehand into friends and non friends and map over those items which would actually be more straightforward for another engineer to see.
React wants keys in the markup for the iterated items created. That is how React creates the relationships between components in the state tree.
I think that you are trying to filter the atribute, and not the list. Try to change this:
this.props.list.friend.filter
to this:
this.props.list.filter
{items.filter((product) => {
if (searchterm === "") {
return product
}
else if (product.title.toLowerCase().includes(searchterm.toLocaleLowerCase())) {
return product
}
})
I'm trying make an AJAX call to get server data into my React Components.
I'm unable to display it with React. I get this error:
Uncaught TypeError: Cannot read property 'map' of undefined
I've done research http://andrewhfarmer.com/react-ajax-best-practices/ and reactjs - Uncaught TypeError: Cannot read property 'map' of undefined ;however, I'm not sure how to map it to my react component.
Here is my code below:
var items;
$.get("http://localhost:3000/getProducts", function( data ) {
items = data;
this.state.items = data;
});
/*React Code Below */
var RepeatModule = React.createClass({
getInitialState: function() {
return { items: [] }
},
render: function() {
var listItems = this.props.items.map(function(item) {
return (
<div className='brick'>
<div>
<a target='_blank' href={item.productURL}><img src={item.imageURL}/></a>
<p className='itemName'>Short Sleeve Oxford Dress Shirt, White, Large</p>
<p className='storeName'>Nike Factory Store</p>
<img className='foundPicture' src='../images/rohit.png'/>
</div>
</div>
);
});
return (
<div>
{listItems}
</div>
);
}
});
ReactDOM.render(<RepeatModule items={items} />,
document.getElementById('clothing-content'));
My JSON array with item properties are valid:
Here is the array below:
[ { _id: 584d1e36a609b545b37611ac,
imageURL: 'http://ih1.redbubble.net/image.252113981.3904/ra,unisex_tshirt,x1350,fafafa:ca443f4786,front-c,30,60,940,730-bg,f8f8f8.u2.jpg',
productName: 'Drake',
productType: 'T-Shirts & Hoodies',
price: '$29.97',
productURL: 'http://www.redbubble.com/people/misfitapparel/works/22923904-drake?grid_pos=6&p=t-shirt',
__v: 0 } ]
Why is this error occurring? And how should it be implemented?
Javascript is asynchronous. Your get function callback does not block program flow, it executes at a later time. The rest of your code will continue to execute. You're sending an AJAX request asynchronously, then you're rendering a react component with an undefined variable. Then, at a later time, the get request will finish and your data will be populated, but this is long after rendering has completed.
The simplest solution here is to only render the component once your AJAX request has finished:
var RepeatModule = React.createClass({
render: function() {
var listItems = this.props.items.map(function(item) {
return (
<div className='brick'>
<div>
<a target='_blank' href={item.productURL}><img src={item.imageURL}/></a>
<p className='itemName'>Short Sleeve Oxford Dress Shirt, White, Large</p>
<p className='storeName'>Nike Factory Store</p>
<img className='foundPicture' src='../images/rohit.png'/>
</div>
</div>
);
});
return (
<div>
{listItems}
</div>
);
}
});
$.get("http://localhost:3000/getProducts", function( data ) {
ReactDOM.render(<RepeatModule items={data} />,
document.getElementById('clothing-content'));
});
A better solution, depending on your needs, is probably to do the AJAX request in a componentDidMount lifecycle method, and store the result in state instead of props.
var RepeatModule = React.createClass({
getInitialState: function() {
return { items: this.props.items || [] }
},
componentWillMount: function() {
console.log("componentWillMount()")
$.get("http://localhost:3000/getProducts", function( data ) {
this.setState({ items : data })
console.log(data,"data is here");
}.bind(this));
},
render: function() {
var listItems = this.state.items.map(function(item) {
return (
<ListItem item={item}/>
);
});
return (
<div>
{listItems}
</div>
);
}
});
/* make the items stateless */
var ListItem = function(props) {
return (
<div className='brick' key={props.item._id}>
<div>
<a target='_blank' href={props.item.productURL}><img src={props.item.imageURL}/></a>
<p className='itemName'>Short Sleeve Oxford Dress Shirt, White, Large</p>
<p className='storeName'>Nike Factory Store</p>
<img className='foundPicture' src='../images/rohit.png'/>
</div>
</div>
);
}
var data = []
ReactDOM.render(<RepeatModule items={data} />, document.getElementById('clothing-content'));
I had to bind the data and use componentWillMount.
http://jsfiddle.net/adamchenwei/3rt0930z/20/
I just trying to create an example to learn how state works in a list.
What I intent to do is to allow a particular value that got repeated in a list, to change, in ALL items in the list, by using state. For example, in this case, I want to change all the list item's name to 'lalala' when I run changeName of onClick.
However I have this warning (issue at fiddle version 11, resolved at version 15)
Any help on resolving it to achieve purpose above?
Actual Code
var items = [
{ name: 'Believe In Allah', link: 'https://www.quran.com' },
{ name: 'Prayer', link: 'https://www.quran.com' },
{ name: 'Zakat', link: 'https://www.quran.com' },
{ name: 'Fasting', link: 'https://www.quran.com' },
{ name: 'Hajj', link: 'https://www.quran.com' },
];
var ItemModule = React.createClass({
getInitialState: function() {
return { newName: this.props.name }
},
changeName() {
console.log('changed name');
this.setState({ newName: 'lalala' });
},
render() {
//<!-- <a className='button' href={this.props.link}>{this.props.name}</a> -->
return (
<li onClick={this.changeName}>
{this.state.newName}
</li>
);
}
});
var RepeatModule = React.createClass({
getInitialState: function() {
return { items: [] }
},
render: function() {
var listItems = this.props.items.map(function(item) {
return (
<div>
<ItemModule
key={item.name}
name={item.name} />
</div>
);
});
return (
<div className='pure-menu'>
<h3>Islam Pillars</h3>
<ul>
{listItems}
</ul>
</div>
);
}
});
ReactDOM.render(<RepeatModule items={items} />,
document.getElementById('react-content'));
-UPDATE-
fiddle version 16
updated fidle, now there is issue with key, also, the onClick did not update the value for all the list item. Is there something wrong I did?
-UPDATE-
fiddle version 20
Now the only issue is change all the list item's name to 'lalala' when I run changeName of onClick.
remove the parenthesis from
onClick={this.changeName()},
so
onClick={this.changeName}
you want to call the function onClick, but you are calling it on render that way
I think you meant to do onClick={this.changeName}
In the way you have it you are calling the changeName function on render instead of on click.
I'm learning react and I'm stuck on how to render the birthdays within my this.state. I figured I would use something like:
{this.state.birthdays}
but that doesn't seem to reach each birthday. My getElementByID is equal to a container which exists on my HTML. Any advice/help would be great!
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
birthdays: {
'January': [{
name: 'Mike',
date: '1/14/90'
}, {
name: 'Joe',
date: '1/7/92'
}],
March: [{
name: 'Mary',
date: '3/7/88'
}]
}
}
}
render() {
return (
<div>
</div>
);
}
}
ReactDOM.render(<App />,
document.getElementById('container'));
Try this:
{ Object.keys(this.state.birthdays).map(this.renderBirthdays) }
And then above your render function create a function called renderBirthdays like this:
renderBirthdays: function(key) {
return (
<div key={key} index={key} details={this.state.birthdays[key]}>
{details.name} - {details.date}
</div>
)
},
render: function() {
return (
<div>{ Object.keys(this.state.birthdays).map(this.renderBirthdays) }</div>
)
}
So you can take advantage of javascripts map which will take your object and key them. Then we're going to pass this key into a function called renderBirthdays which will iterate over the item. We need to pass a key and an index into the element, and for ease of use, we can pass a details prop into it equal to the currently selected item it's iterating over. That way we can just use {details.name} etc in the element.
This is untested, but something like this should work. Loop over the month keys using Object.keys, then reduce each set of birthdays to a flat array:
render() {
return (
<div>
{Object.keys(this.state.birthdays).reduce((birthdays, month) => {
return birthdays.concat(this.state.birthdays[month].map((bday) => {
return (
<p>{bday.name} - {bday.date}</p>
);
}));
}, [])}
</div>
);
}
I am building an app to learn React, and am using Firebase as my data storage. I have all the items in my firebase rendering out, and am trying to enable removal of individual items. However, when I try to remove, I get Uncaught TypeError: Cannot read property 'name' of null after clicking on the remove button, and it is referring to this line of code in the renderExpenditure function:
<strong>{details.name}</strong>, <strong>{h.formatPrice(details.amount)}</strong>, {details.category}, {details.type}, {details.date}
The state is set up as follows:
getInitialState: function() {
return {
cashbook: {
expenditure: {},
income: {}
},
totals: {},
available: {}
}
}
And the functions which render out the items and try to remove them are as follows:
(Can anyone see what I am doing wrong, or is this too little code to work out what is going on?)
Thanks in advance.
Within App
render: function() {
return(
<div className="cashbook">
<div className="expenditure">
<ul className="items">
{Object.keys(this.state.cashbook.expenditure).map(this.renderExpenditure)}
</ul>
</div>
</div>
);
}
renderExpenditure: function(key) {
var details = this.state.cashbook.expenditure[key];
return(
<li className="item" key={key}>
<strong>{details.name}</strong>, <strong>{h.formatPrice(details.amount)}</strong>, {details.category}, {details.type}, {details.date}
<button className="remove-item" onClick={this.removeExpenditure.bind(null, key)}>Remove</button>
</li>
);
},
removeExpenditure: function(key) {
this.state.cashbook.expenditure[key] = null;
this.setState({
expenditure: this.state.cashbook.expenditure
});
},
You are setting the wrong value in setState. expenditure doesn't exist in the root state, so you must overwrite the parent that contains it. It should be:
this.state.cashbook.expenditure[key] = null;
this.setState({
cashbook: this.state.cashbook
});