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.
Related
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'));
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
});
I have a Legend, which contains multiple Legend.Items children. I'm having a problem where currently onClick it is possible to deselect all of the Legend Items which has consequences that I'd like to avoid. Is it possible to set some sort of onClick handler in the Legend component that can have some state clicked and check whether there are n - 1 legend items "selected/faded", n being the total number of legend items? I looked at the JSX Spread Attributes, but because I'm using {this.props.children}, I'm not sure how to use them or if they would work in this context.
I also took a look at this blogpost (http://jaketrent.com/post/send-props-to-children-react/), but it looked a bit hacky to me and I thought there might be a more conventional way. I'm new to ReactJS so if I need to provide more context, let me know!
MY CODE:
LEGEND.JSX
var React = require('react');
var cn = require('classnames');
// Have legend hold state about how many clicked
var Legend = React.createClass({
getInitialState: function () {
return { clicked: 0 }
},
render: function () {
console.log(this.props.children);
return (
<ul className="legend inline-list">
{this.props.children}
</ul>
);
},
});
Legend.Item = React.createClass({
getInitialState: function () {
return { hover: false, clicked: false };
},
handleMouseOver: function () {
this.setState({ hover: true });
this.props.mouseOver(this.props.name);
},
handleMouseOut: function () {
this.setState({ hover: false });
this.props.mouseOut(this.props.name);
},
handleClick: function() {
if (this.state.clicked) {
this.setState({ clicked: false });
this.props.click(this.props.name);
} else {
this.setState({ clicked: true });
this.props.click(this.props.name);
};
},
render: function () {
var swatchClasses = cn({ 'swatch': true, 'legend-item-fade': this.state.hover, 'c3-legend-item-hidden': this.state.clicked })
var spanClasses = cn({ 'legend-item-fade': this.state.hover, 'c3-legend-item-hidden': this.state.clicked })
return (
<li className="legend-item">
<i className={swatchClasses}
onClick={this.handleClick}
onMouseEnter={this.handleMouseOver}
onMouseLeave={this.handleMouseOut}
style={{ "backgroundColor": this.props.color }}></i>
<span className={spanClasses}
onClick={this.handleClick}
onMouseEnter={this.handleMouseOver}
onMouseLeave={this.handleMouseOut}>
{this.props.name}
</span>
</li>
);
},
});
module.exports = {
Legend: Legend,
};
RESPONSE.JSX RENDER FUNCTION
<Legend>
{newColumns.map(function (column) {
return (
<Legend.Item name={column.name}
color={column.color}
click={this.onLegendClick}
mouseOut={this.onLegendMouseOut}
mouseOver={this.onLegendMouseOver}/>
);
}.bind(this))}
</Legend>
I think the best and simplest way is to use callbacks.
In Legend recreate the components from the children, augmenting their props with a callback to Legend:
let legendItems = React.Children.map(this.props.children, child =>
React.cloneElement(child, { updateLegendCounter: this.updateLegend})
);
The callback in Legend is something like this:
updateLegend() {
this.setState({clicked: clicked + 1})
}
And finally, in your render method, you discriminate when
if (this.state.clicked === children.length-1)
Also, I would pass the initial state of clicked as a prop to the Item element. In this way it becomes really easy to select/deselect all.
I am running through a react tutorial on tutsplus that is a bit old, and the code doesn't work as it was originally written. I actually am totally ok with this as it forces me to learn more independently, however I have spent a while on a bug that I just can't figure out. The bug consists of not being able to pass on an objects key, which prevents my program from updating the state of the correct object.
First off here is the repo if you want to run this code and see it in action: https://github.com/camerow/react-voteit
I have a child component that looks like this:
var FeedItem = React.createClass({
vote: function(newCount) {
console.log("Voting on: ", this.props, " which should have a key associated.");
this.props.onVote({
key: this.props.key,
title: this.props.title,
description: this.props.desc,
voteCount: newCount
});
},
voteUp: function() {
var count = parseInt(this.props.voteCount, 10);
var newCount = count + 1;
this.vote(newCount);
},
voteDown: function() {
var count = parseInt(this.props.voteCount, 10);
var newCount = count - 1;
this.vote(newCount);
},
render: function() {
var positiveNegativeClassName = this.props.voteCount >= 0 ?
'badge badge-success' :
'badge badge-danger';
return (
<li key={this.props.key} className="list-group-item">
<span className={positiveNegativeClassName}>{this.props.voteCount}</span>
<h4>{this.props.title}</h4>
<span>{this.props.desc}</span>
<span className="pull-right">
<button id="up" className="btn btn-sm btn-primary" onClick={this.voteUp}>↑</button>
<button id="down" className="btn btn-sm btn-primary" onClick={this.voteDown}>↓</button>
</span>
</li>
);
}
});
Now when someone hits the vote button the desired behavior is for the FeedItem.vote() method to send an object up to the main Feed component:
var FeedList = React.createClass({
render: function() {
var feedItems = this.props.items;
return (
<div className="container">
<ul className="list-group">
{feedItems.map(function(item) {
return <FeedItem key={item.key}
title={item.title}
desc={item.description}
voteCount={item.voteCount}
onVote={this.props.onVote} />
}.bind(this))}
</ul>
</div>
);
}
});
Which should pass that key on throught the parent component's onVote function:
var Feed = React.createClass({
getInitialState: function () {
var FEED_ITEMS = [
{
key: 1,
title: 'JavaScript is fun',
description: 'Lexical scoping FTW',
voteCount: 34
}, {
key: 2,
title: 'Realtime data!',
description: 'Firebase is cool',
voteCount: 49
}, {
key: 3,
title: 'Coffee makes you awake',
description: 'Drink responsibly',
voteCount: 15
}
];
return {
items: FEED_ITEMS,
formDisplayed: false
}
},
onToggleForm: function () {
this.setState({
formDisplayed: !this.state.formDisplayed
});
},
onNewItem: function (newItem) {
var newItems = this.state.items.concat([newItem]);
// console.log("Creating these items: ", newItems);
this.setState({
items: newItems,
formDisplayed: false,
key: this.state.items.length
});
},
onVote: function (newItem) {
// console.log(item);
var items = _.uniq(this.state.items);
var index = _.findIndex(items, function (feedItems) {
// Not getting the correct index.
console.log("Does ", feedItems.key, " === ", newItem.key, "?");
return feedItems.key === newItem.key;
});
var oldObj = items[index];
var newItems = _.pull(items, oldObj);
var newItems = this.state.items.concat([newItem]);
// newItems.push(item);
this.setState({
items: newItems
});
},
render: function () {
return (
<div>
<div className="container">
<ShowAddButton displayed={this.state.formDisplayed} onToggleForm={this.onToggleForm}/>
</div>
<FeedForm displayed={this.state.formDisplayed} onNewItem={this.onNewItem}/>
<br />
<br />
<FeedList items={this.state.items} onVote={this.onVote}/>
</div>
);
}
});
My logic relies on being able to reconcile the keys in the onVote function, however the key prop is not being properly passed on. So my question is, how do I pass on they key through this 'one way flow' to my parent component?
Note: Feel free to point out other problems or better design decision, or absolute stupidities. Or even that I'm asking the wrong question.
Looking forward to a nice exploration of this cool framework.
The key prop has a special meaning in React. It is not passed to the component as a prop, but is used by React to aid the reconciliation of collections. If you know d3, it works similar to the key function for selection.data(). It allows React to associate the elements of the previous tree with the elements of the next tree.
It's good that you have a key (and you need one if you pass an array of elements), but if you want to pass that value along to the component, you should another prop:
<FeedItem key={item.key} id={item.key} ... />
(and access this.props.id inside the component).
I have a dynamic list of children, that are form inputs.
ex:
var FormRows = React.createClass({
getInitialState: function() {
return {
rows: []
}
},
createRows: function() {
this.props.values.maps(value){
rows.push(<FormRow ...handlers... ...props... value={value} />
}
},
addNewRow{
// add a new row
},
render: function() {
return (
<div>
{this.state.rows}
</div>
);
});
var FormRow = React.createClass({
getInitialState: function() {
return {
value: this.props.value || null
}
},
render: function() {
<input type='text' defaultValue={this.state.value} ...changeHandler ... }
}
});
This is a dumbed down version , but the idea, is a its a dynamic form, where the user can click a plus button to add a row, and a minus button, which will set the row to visibility to hidden.
This state is nested n levels deep. What is the best way to actually get the state out of the children, and submit the form? I can use 'ref' add a function to getFormValue(): { return this.state.value } to the FormRow button, but i'm not sure if thats the best practice way.
I find myself using this pattern quite often, an array of undetermined size of children, that need to pass the state up.
Thanks
It’s not a dumb question at all, and a good example of using flux principals in React. Consider something like this:
var App
// The "model"
var Model = {
values: ['foo', 'bar'],
trigger: function() {
App.forceUpdate()
console.log(this.values)
},
update: function(value, index) {
this.values[index] = value
this.trigger()
},
add: function() {
this.values.push('New Row')
this.trigger()
}
}
var FormRows = React.createClass({
addRow: function() {
Model.add()
},
submit: function() {
alert(Model.values);
},
render: function() {
var rows = Model.values.map(function(value, index) {
return <FormRow key={index} onChange={this.onChange} index={index} value={value} />
}, this)
return (
<div>{rows}<button onClick={this.addRow}>Add row</button><button onClick={this.submit}>Submit form</button></div>
)
}
})
var FormRow = React.createClass({
onChange: function(e) {
Model.update(e.target.value, this.props.index)
},
render: function() {
return <input type='text' defaultValue={this.props.value} onChange={this.onChange} />
}
});
App = React.render(<FormRows />, document.body)
I used a simplified model/event example using Array and forceUpdate but the point here is to let the model "own" the form data. The child components can then make API calls on that model and trigger a re-render of the entire App with the new data (Flux).
Then just use the model data on submit.
Demo: http://jsfiddle.net/ekr41bzr/
Bind values of inputs to some model (for example build in Backbone or Flux) and on submit retrieve values from there, without touching inputs.