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.
Related
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.
I have this function that sets the date from two weeks ago:
dateTwoWeeksAgo: function(){
var twoWeeksAgo = new Date().toDateString();
this.setState({twoWeeksAgo: twoWeeksAgo});
},
I have this code that calls this function. But it is not working. how do I display a variable I am setting the state of or returning from a function?
<h2 className="headings" id="commitTotal"> Commits since {this.dateTwoWeeksAgo} : {this.state.commits.length} </h2>
Option 1: In order to display the value of twoWeeksAgo held on you state you can:
<h2 className="headings" id="commitTotal"> Commits since {this.state.twoWeeksAgo} : {this.state.commits.length} </h2>
The actual method which updates your state - dateTwoWeeksAgo() - could be called for example in the componendDidMount lifefycle method.
https://facebook.github.io/react/docs/component-specs.html#mounting-componentdidmount
Here is a demo: http://codepen.io/PiotrBerebecki/pen/LRAmBr
Option 2: Alternatively, you can just call a method which returns the required date like so (http://codepen.io/PiotrBerebecki/pen/NRzzaX),
const App = React.createClass({
getInitialState: function() {
return {
commits: ['One', 'Two']
};
},
dateTwoWeeksAgo: function() {
return new Date().toDateString();
},
render: function() {
return (
<div>
<h2 className="headings" id="commitTotal"> Commits since {this.dateTwoWeeksAgo()} : {this.state.commits.length} </h2>
</div>
);
}
})
Code option 1:
const App = React.createClass({
getInitialState: function() {
return {
twoWeeksAgo: null,
commits: ['One', 'Two']
};
},
componentDidMount: function() {
this.dateTwoWeeksAgo();
},
dateTwoWeeksAgo: function() {
var twoWeeksAgo = new Date().toDateString();
this.setState({twoWeeksAgo: twoWeeksAgo});
},
render: function() {
return (
<div>
<h2 className="headings" id="commitTotal"> Commits since {this.state.twoWeeksAgo} : {this.state.commits.length} </h2>
</div>
);
}
})
ReactDOM.render(
<App />,
document.getElementById('app')
);
It should be:
<h2 className="headings" id="commitTotal"> Commits since {this.state.dateTwoWeeksAgo} : {this.state.commits.length} </h2>
The difference is this.state.dateTwoWeeksAgo
For your example of code I suggest this approach
dateTwoWeeksAgo: function(){
return new Date().toDateString();
},
<h2 className="headings" id="commitTotal"> Commits since {this.dateTwoWeeksAgo()} : {this.state.commits.length} </h2>
if you really want to use state you need to change {this.dateTwoWeeksAgo} to {this.state.dateTwoWeeksAgo}
I saw some questions speaking about similar issues but somehow I still do not manage to solve my issue so here I am asking for your kind help. I am pretty new to React and would like to send a function from a Parent to a child and then use it from the Child but somehow when I want to use it it says
Uncaught TypeError: Cannot read property 'props' of undefined"
Edited Code after first answers were helping:
var Menu = React.createClass({
links : [
{key : 1, name : "help", click : this.props.changePageHelp}
],
render : function() {
var menuItem = this.links.map(function(link){
return (
<li key={link.key} className="menu-help menu-link" onClick={link.click}>{link.name}</li>
)
});
return (
<ul>
{menuItem}
</ul>
)
}
});
var Admin = React.createClass ({
_changePageHelp : function() {
console.log('help');
},
render : function () {
return (
<div>
<div id="menu-admin"><Menu changePageHelp={this._changePageHelp.bind(this)} /></div>
</div>
)
}
});
ReactDOM.render(<Admin />, document.getElementById('admin'));
Pass a value from Menu function and recieve it in the changePageHelp function and it works.
var Menu = React.createClass({
render : function() {
return (
<div>
{this.props.changePageHelp('Hello')}
</div>
)
}
});
var Admin = React.createClass ({
_changePageHelp : function(help) {
return help;
},
render : function () {
return (
<div>
<div id="menu-admin"><Menu changePageHelp={this._changePageHelp.bind(this)} /></div>
</div>
)
}
});
ReactDOM.render(<Admin />, document.getElementById('admin'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="admin"></div>
For performance reasons, you should avoid using bind or arrow functions in JSX props. This is because a copy of the event handling function is created for every instance generated by the map() function. This is explained here: https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
To avoid this you can pull the repeated section into its own component. Here is a demo: http://codepen.io/PiotrBerebecki/pen/EgvjmZ The console.log() call in your parent component receives now the name of the link. You could use it for example in React Router.
var Admin = React.createClass ({
_changePageHelp : function(name) {
console.log(name);
},
render : function () {
return (
<div>
<div id="menu-admin">
<Menu changePageHelp={this._changePageHelp} />
</div>
</div>
);
}
});
var Menu = React.createClass({
getDefaultProps: function() {
return {
links: [
{key: 1, name: 'help'},
{key: 2, name: 'about'},
{key: 3, name: 'contact'}
]
};
},
render: function() {
var menuItem = this.props.links.map((link) => {
return (
<MenuItem key={link.key}
name={link.name}
changePageHelp={this.props.changePageHelp}
className="menu-help menu-link" />
);
});
return (
<ul>
{menuItem}
</ul>
);
}
});
var MenuItem = React.createClass ({
handleClick: function() {
this.props.changePageHelp(this.props.name);
},
render : function () {
return (
<li onClick={this.handleClick}>
Click me to console log in Admin component <b>{this.props.name}</b>
</li>
);
}
});
ReactDOM.render(<Admin />, document.getElementById('admin'));
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 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
});