I have a list with around 2k items. If I use onClick on each child, I would end up with 2k listeners which is what I have currently. I would want to do something like making the parent component listen to the click events instead. But if I do that, I don't have reference to the child component which I need to call setState on. Also the list of child components can be filtered dynamically (using this.refs might be bad ?).
The best I can come up with is to make a hash of child components id mapping to child components in the parent and look up the view on click.
Just for illustration purposes:
var Parent = React.createClass({
shouldComponentUpdate: function() { return false; },
handler: function(e) {
// look up from reference and set state
},
componentWillUnmount: function() {
// clean up reference
},
render: function() {
this.reference = {};
var items = [];
for(var i = 0; i < this.props.items.length; i++) {
var child = React.createClass(Child, {id: this.props.items[i].id});
items.push(child);
reference[child.id] = child;
}
return React.createClass('div', {onClick: this.handler}, items);
}
})
I wonder if there is a React way of dealing with this.
I think this answer may help... It does not matter if you have 2000 event handlers or just one. React deals with it in the same way. Remember that the HTML you return in your render method does not get added to the DOM but it is just used by React to build a virtual DOM representation. In the end, React has only one onClick.
React efficient way to bind event to many dom elements
If you need to know what element triggered the click you just need to access event.target and use any data-attribute to identify the clicked element.
The React way of doing this would be to use a Flux dispatcher + a Store. Basically, you can have each item bind to an event that gets triggered from the store once the store has carried out the tasks you want it to complete.
So the flow will be:
Item gets clicked => Flux event is dispatched => Flux dispatcher hears the events and executes the appropriate function with the data passed from Item component.
var ItemStore = {
doSomething: function(data){
// do something with the data
}
}
MicroEvent.mixin(ItemStore);
var AppDispatcher = new Dispatcher();
AppDispatcher.register(function(payload) {
switch (payload.eventName) {
case 'item-clicked':
ItemStore.doSomething(payload.data.someData);
ItemStore.trigger('did-something');
}
return true;
})
var Item = React.createClass({
shouldComponentUpdate: function() { return false; },
componentDidMount: function() {
ItemStore.bind('did-something', this.submitHandled);
},
handler: function(e) {
AppDispatcher.dispatch({
eventName: 'item-clicked',
data: {
someData: 'sample data'
}
});
},
componentWillUnmount: function() {
// clean up reference
},
submitHandled: function() {
// do something after the click
},
render: function() {
// insert your item's html here.
}
})
Building on #damianmr's answer, here's an example.
var Child = React.createClass({
shouldComponentUpdate(nextProps){
if (this.props.text !== nextProps.text) return true;
if (this.props.active !== nextProps.active) return true;
return false;
},
render(){
var className = 'Child';
if (this.props.active) className += ' Child-active';
return (
<div {...this.props} className={className}>
{this.props.text}
</div>
);
}
});
var Parent = React.createClass({
getInitialState(){
return {active: -1};
},
setActive(id){
this.setState({active: id});
},
render(){
return (
<div>
{this.props.items.map((item) => {
return (
<Child
active={this.state.active === item.id}
onClick={() => this.setActive(item.id)}
text={'My id is ' + item.id}
key={item.id}
/>
);
})}
</div>
);
}
});
Related
So I'm rendering components from an array 'values': ["hello", "world] successfully but I would like to add a button component so that every time it gets clicked, another empty field shows up. This is what it currently looks like:
but i would like it so that there is a button and every time I click on it, it renders another empty component to input text. Would it be correct to add a button component directly inside the my array_node.jsx file? Is what I'm doing correct so far? Would I also have to add some sort of newInput: function() in side the var AddButton = React.createClass({})? Thank you!
array_node.jsx:
{...
childChange: function(name, valid, value) {
// update state
this.state.values = this.props.values;
// Using regex to find last digits from 0-9
var pattern = /[0-9]/;
var match = name.match(pattern);
// Parse char into int
var i = parseInt(match);
this.state.values[i] = value;
this.setState(this.state);
// Call parent callback
this.props.callback(
this.props.name,
this.props.node.valid(this.state.values),
this.state.values
);
},
addItem: function(values){
},
render: function() {
var that = this;
return (
<div id = "form">
{this.props.values.map(function(v, i) {
return (
<div>
{(that.props.node.get().constructor.name === "Parent") ?
<ParentComponent
name={that.props.name + i}
key={i}
timer={that.props.timer}
callback={that.childChange}
values={v}
newParent={that.props.node.get()}
/>
:
<NodeComponent
name={that.props.name + i}
key={i}
timer={that.props.timer}
callback={that.childChange}
value={v}
newNode={that.props.node.get()}
/>
}
</div>
)
})}
</div>
)
}
});
return ArrayNodeComponent
var AddButton = React.createClass({
addItem: function() {
},
render: function() {
return(
<div id="create_new_entry">
</div>
)
}
})
formatoc:
var props = {
'name' : 'form',
'timer' : 1500,
'callback' : function(id, validity, value) {console.log(id, validity, value);},
'values': ["hello", "world"],
'node' : new FormatOC.ArrayNode({"__array__":"unique", "__type__":"string","__minimum__":1,"__maximum__":200,"__component__":"Input"},
)
}
React.render(React.createElement(ArrayNodeComponent, props), document.getElementById('react-component'));
You might add a button into your form within the render function.
Then listen to clicks and add a new empty element to your values list.
if you would like to propagate the changes to some parent component, you would have to pass the onClick handler from the parent component and update the values list there too.
import { Component } from 'react';
class ArrayNodeComponent extends Component {
// other code ...
// like your initialisation of your state
// and other functions
addEmptyItem() {
const { values } = this.state;
this.setState({
values: [...values, ""]
});
}
render() {
return (
<form id="test">
{
/* this is your values map routine, shortened */
this.props.values.map(function(v, i) { /*...*/ })
}
<button onClick={() => this.addEmptyItem()}>Add</button>
</form>
);
}
}
Btw in this simple scenario, it would not make sense to create a custom Button component.
I am struggling with wrapping my head around sending information from an onClick event listener in my child component to my parent component. I am rendering an array of children, and I want each to have an event listener, so that onClick, the name of that child will be set as the "selected" parameter in my parent state.
This is my child component.
var DropDownLink = React.createClass({
render: function(){
var dropDownVisible;
if (this.props.visible){
dropDownVisible="dropdownoption"
} else {
dropDownVisible="dropdownoption hide"
}
return (
<div onClick={this.props.onClick} className={dropDownVisible}>{this.props.name}</div>
);
}
});
And this is my parent component:
var Dropdown = React.createClass({
getInitialState: function() {
return {
listVisible: false,
selected: this.props.items[0].name,
};
},
show: function() {
this.setState({ listVisible: true });
document.addEventListener("click", this.hide);
},
hide: function() {
this.setState({ listVisible: false });
document.removeEventListener("click", this.hide);
},
generateDropDown: function(item) {
return <DropDownLink name={item.name} visible={this.state.listVisible}/>
},
render: function() {
var items = this.props.items.map(this.generateDropDown);
var dropdowncontain;
if (this.state.listVisible) {
dropdowncontain="dropdowncontainer";
} else {
dropdowncontain="dropdowncontainer hide";
}
return (
<span className="roledropdown" onClick={this.show}>
<span className="roleselected">{this.state.selected}</span>
<i className="fa fa-caret-down clickdown"></i>
<div className={dropdowncontain}>{items}</div>
</span>
)
}
});
I believe I have set up the child component correctly based on some examples I saw, however what is confusing me is how I should write the function in the parent that handles each child index, given that I am already mapping the array using my generateDropDown function. Ideally, I want to just modify the generateDropDown function, to include this functionality, but I am new to React and struggling to understand how to.
Here's an example of what you could change in your Dropdown component. Essentially, pass in a onClick handler to your children with some unique identifier bound to each. In this example, it is bound to the item.name because that is in your original code. You could use something else from the item object, though.
handleChildClick: function(childName, event){
// One of the DropDownLink components was clicked.
// Handle that click here.
},
generateDropDown: function(item){
return <DropDownLink name={item.name} onClick={this.handleChildClick.bind(item.name)} visible={this.state.listVisible}/>
},
I have following React.js application structure:
<App />
<BreadcrumbList>
<BreadcrumbItem />
<BreadcrumbList/>
<App />
The problem is, when I click on <BreadcrumbItem /> , I want to change a state in <App />
I used callback to pass props to <BreadcrumbList/> but that`s how far I got.
Is there any pattaren how to easily pass props up to compenent tree ?
How can I pass prop to <App />, without doing any callback chaining ?
If you are doing something simple then its often just better to pass the change in state up through the component hierarchy rather than create a store specifically for that purpose (whatever it may be). I would do the following:
BreadcrumbItem
var React = require('react/addons');
var BreadcrumbItem = React.createClass({
embiggenMenu: function() {
this.props.embiggenToggle();
},
render: function() {
return (
<div id="embiggen-sidemenu" onClick={this.embiggenMenu} />
);
}
});
module.exports = BreadcrumbItem ;
THEN pass it up to the parent through the BreadcrumbList component.....
<BreadcrumbItem embiggenToggle={this.props.embiggenToggle}>
... and UP to App, then use it to set the state....
var React = require('react/addons');
var App = React.createClass({
embiggenMenu: function() {
this.setState({
menuBig: !this.state.menuBig
});
},
render: function() {
return (
<div>
<BreadcrumbList embiggenToggle={this.embiggenMenu} />
</div>
)
}
});
module.exports = BreadcrumbItem;
This example toggles a simple boolean however you can pass up anything you like. I hope this helps.
I have not tested this but it was (quickly) ripped from a live working example.
EDIT:
As it was requested i'll expand upon the vague: "you can pass up anything".
If you were making a navigation menu based on an array and needed to pass up the selected item to a parent then you would do the following
var React = require('react/addons');
var ChildMenu = React.createClass({
getDefaultProps: function () {
return {
widgets : [
["MenuItem1"],
["MenuItem2"],
["MenuItem3"],
["MenuItem4"],
["MenuItem5"],
["MenuItem6"],
["MenuItem7"]
]
}
},
handleClick: function(i) {
console.log('You clicked: ' + this.props.widgets[i]);
this.props.onClick(this.props.widgets[i]);
},
render: function() {
return (
<nav>
<ul>
{this.props.widgets.map(function(item, i) {
var Label = item[0];
return (
<li
onClick={this.handleClick.bind(this, i)}
key={i}>
{Label}
</li>
);
}, this)}
</ul>
</nav>
);
}
});
module.exports = ChildMenu;
You would then do the following in the parent:
var React = require('react/addons');
var ChildMenuBar = require('./app/top-bar.jsx');
var ParentApp = React.createClass({
widgetSelectedClick: function(selection) {
//LOGGING
//console.log('THE APP LOGS: ' + selection);
//VARIABLE SETTING
var widgetName = selection[0];
//YOU CAN THEN USE THIS "selection"
//THIS SETS THE APP STATE
this.setState({
currentWidget: widgetName
});
},
render: function() {
return (
<ChildMenu onClick={this.widgetSelectedClick} />
);
}
});
module.exports = ParentApp;
I hope this helps. Thanks for the upvote.
If you use Flux pattern, you can have a AppStore which listen a BREADCRUMB_CLICK event. So when you click on a BreadCrumbItem, you can execute an action which dispatch BREADCRUMB_CLICK event. When AppStore handle the event, he inform App component which update your state.
For more informations:
Flux architecture
I have a parent React class (<EventList />) that contains an object that store's it's Child Component's (<Event />) data. I've omitted many of the functions for the sake of brevity.
Background on the State of the EventList
/**
* The events state looks like this before the EventList component is rendered:
*
* var events = {
* 1: {
* id: 1,
* title: "Some title"
* },
* 2: {
* id: 2,
* title: "Some other title"
* },
*
* ...
* };
*/
Event.jsx
var Event = React.createClass({
/**
* Pass up the ID of the Event and the new value of the Event's Title
*/
_handleChange: function (e) {
this.props.handleChange(this.props.id, e.target.value);
},
render: function () {
return (
<div className="event">
<input type="text" value={this.props.title} onChange={this._handleChange} />
</div>
);
}
});
EventList.jsx
var EventList = React.createClass({
propTypes: {
events: React.PropTypes.object
},
/**
* Update the State of an event who's title has changed
*/
_handleChange: function (id, title) {
var newState = React.addons.update(this.state.events[id].title, {
$set: title
});
this.setState(newState);
},
render: function () {
var renderedEvents = Object.keys(this.state.events).map(function (id) {
var event = this.state.events[id];
return <Event key={event.id} title={event.title} handleChange={this._handleChange}/>;
}, this);
return (
<div className="events">
{renderedEvents}
</div>
);
}
});
Now this is fine, and it works. The state of the title gets updated and everything renders and re-renders successfully; but that's also the problem:
Everything re-renders!
It's not bad with a few events in the list, but once there's a good number of them the re-render takes a huge performance toll as the EventList render function goes through and populates a new array of <Event /> components.
One thing I'd like to be able to do (though assume it'll require a complete restructuring of the application) is to be able to utilize shouldComponentUpdate within the <Event /> component.
However, with my current relationship I can't do this. If you take a look at the default params for shouldComponentUpdate:
shouldComponentUpdate: function(nextProps, nextState) {...},
You'll notice that at the <Event /> level, this.props will always equal nextProps, so trying to do something like:
shouldComponentUpdate: function(nextProps, nextState) {
return this.props !== nextProps;
},
will always return false because at this point in the flow of things they are pointing to the exact same set of data. nextState of course does not exist at the <Event /> level.
So my question is, what do I need to do to get rid of the terribly costly re-render at the <EventList /> level?
The problem is in your update call. Currently, you essentially do var newState = title. You need to actually update the top level state key.
_handleChange: function (id, title) {
var update = {};
update[id] = {title: {$set: title}};
var newEvents = React.addons.update(this.state.events, update);
this.setState({events: newEvents});
},
Or with ES6 you can avoid the local variable:
_handleChange: function (id, title) {
var newEvents = React.addons.update(this.state.events, {
[id]: {
title: {$set: title}
}
});
this.setState({events: newEvents});
},
I was really disappointed by the performance I got on the following simple ReactJS example. When clicking on an item, the label (count) gets updated accordingly. Unfortunately, this takes roughly ~0.5-1 second to get updated. That's mainly due to "re-rendering" the entire todo list.
My understanding is that React's key design decision is to make the API seem like it re-renders the whole app on every update. It is supposed take the current state of the DOM and compare it with the target DOM representation, do a diff and update only the things that need to get updated.
Am I doing something which is not optimal? I could always update the count label manually (and the state silently) and that will be an almost instant operation but that takes away the point of using ReactJS.
/** #jsx React.DOM */
TodoItem = React.createClass({
getDefaultProps: function () {
return {
completedCallback: function () {
console.log('not callback provided');
}
};
},
getInitialState: function () {
return this.props;
},
updateCompletedState: function () {
var isCompleted = !this.state.data.completed;
this.setState(_.extend(this.state.data, {
completed: isCompleted
}));
this.props.completedCallback(isCompleted);
},
render: function () {
var renderContext = this.state.data ?
(<li className={'todo-item' + (this.state.data.completed ? ' ' + 'strike-through' : '')}>
<input onClick={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
<span onClick={this.updateCompletedState} className="description">{this.state.data.description}</span>
</li>) : null;
return renderContext;
}
});
var TodoList = React.createClass({
getInitialState: function () {
return {
todoItems: this.props.data.todoItems,
completedTodoItemsCount: 0
};
},
updateCount: function (isCompleted) {
this.setState(_.extend(this.state, {
completedTodoItemsCount: isCompleted ? this.state.completedTodoItemsCount + 1 : this.state.completedTodoItemsCount - 1
}));
},
render: function () {
var updateCount = this.updateCount;
return (
<div>
<div>count: {this.state.completedTodoItemsCount}</div>
<ul className="todo-list">
{ this.state.todoItems.map(function (todoItem) {
return <TodoItem data={ todoItem } completedCallback={ updateCount } />
}) }
</ul>
</div>
);
}
});
var data = {todoItems: []}, i = 0;
while(i++ < 1000) {
data.todoItems.push({description: 'Comment ' + i, completed: false});
}
React.renderComponent(<TodoList data={ data } />, document.body);
<script src="http://fb.me/react-js-fiddle-integration.js"></script>
jsFiddle link, just in case: http://jsfiddle.net/9nrnz1qm/3/
If you do the following, you can cut the time down by a lot. It spends 25ms to 45ms to update for me.
use the production build
implement shouldComponentUpdate
update the state immutably
updateCompletedState: function (event) {
var isCompleted = event.target.checked;
this.setState({data:
_.extend({}, this.state.data, {
completed: isCompleted
})
});
this.props.completedCallback(isCompleted);
},
shouldComponentUpdate: function(nextProps, nextState){
return nextState.data.completed !== this.state.data.completed;
},
Updated fiddle
(there are a lot of questionable things about this code, daniula points out some of them)
When you are generating list of elements you should provide unique key prop for everyone. In your case:
<ul className="todo-list">
{ this.state.todoItems.map(function (todoItem, i) {
return <TodoItem key={i} data={ todoItem } completedCallback={ updateCount } />
}) }
</ul>
You can find out about this mistake by warning message in browser console:
Each child in an array should have a unique "key" prop. Check the render method of TodoList. See fb.me/react-warning-keys for more information.
There is another warning which you can easily fix by changing event handler on <input type="checkbox" /> inside <TodoItem /> from onClick to onChange:
<input onClick={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
You are doing some string concatenation to set proper className. For more readable code try using nice and simple React.addons.classSet:
render: function () {
var renderContext = this.state.data ?
var cx = React.addons.classSet({
'todo-item': true,
'strike-through': this.state.data.completed
});
(<li className={ cx }>
<input onChange={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
<span onClick={this.updateCompletedState} className="description">{this.state.data.description}</span>
</li>) : null;
return renderContext;
}
I'm looking at where you render() the list...
<div>
<div>count: {this.state.completedTodoItemsCount}</div>
<ul className="todo-list">
{ this.state.todoItems.map(function (todoItem) {
return <TodoItem data={ todoItem } completedCallback={ updateCount } />
}) }
</ul>
</div>
This should not be called every time a TodoItem is updated. Give the above element a surrounding div and an id like this:
return <div id={someindex++}><TodoItem
data={ todoItem }
completedCallback={ updateCount }
/></div>
Then simply rerender a single TodoItem as it is changed, like so:
ReactDOM.render(<TodoItem ...>, document.getElementById('someindex'));
ReactJS is supposed to be fast, yes, but you still need to stick to general programming paradigms, i.e., asking the machine to do as little as possible, thereby producing the result as fast as possible. Rerendering stuff that doesn't need to get re-rendered gets in the way of that, whether or not it's "best practice".