Using backbone-react-component I am trying to build a task list of sorts. If you look at the snipplet provided you will see to core variables tasksList and tasksListChild of which my code thus far seems to render as far as _createTaskChild But Rendering beyond that I have nothing, literally, not even an error in the console window. I put console.log to better elaborate here where things are kind of failing?.. console.log(model.get('task_id')); will put out to the console no problem. console.log('hi') however, does not. I'm hoping by coming here an extra set of eyes can tell me where I messed up.
var tasksListChild = React.createClass({
mixins: [BackboneReactMixin],
render: function () {
console.log('hi')
return (
<span onClick={ this._handleTaskClick }>nothing</span>
);
},
_handleTaskClick: function (event) {
console.log(this.props)
}
});
var tasksList = React.createClass({
mixins: [BackboneReactMixin],
render: function () {
return (
<div className="list-group js__page-list-group-tasks">
{
this.getCollection().map(this._createTaskChild)
}
</div>
);
},
_createTaskChild: function (model) {
console.log(model.get('task_id'));
return (
<a className="list-group-item">
<tasksListChild key={model.get('task_id')} model={model} />
</a>
);
}
});
Will this work.
_createTaskChild: function (model) {
var taskID = model.get('task_id');
console.log(taskID);
return (
<a className="list-group-item">
<tasksListChild key={taskID} model={model} />
</a>
);
}
Related
I decided to learn React and started with the official tutorial. All is good until I get to this state of my code:
var CommentBox = React.createClass({
render: () => {
return (
<div className="commentBox">
<h1> Comments </h1>
<CommentList />
<CommentForm />
</div>
);
}
});
var CommentForm = React.createClass({
render: () => {
return (
<div className="commentForm">
Hello, world! I am a comment form;
</div>
);
}
});
var Comment = React.createClass({
rawMarkup: () => {
var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
return {__html: rawMarkup};
},
render: () => {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2> // <--- [[[[[[ ERROR IS HERE ]]]]]]
<span dangerouslySetInnerHtml={this.rawMarkup} />
</div>
);
}
});
var CommentList = React.createClass({
render: () => {
return (
<div className="commentList">
<Comment author="Pete Hunt">This is one comment</Comment>
<Comment author="Jordan Walke">This is *another* comment yo</Comment>
</div>
);
}
});
ReactDOM.render(
<CommentBox />,
document.getElementById('content')
);
When I try to run it, I get the following error in devtools:
TypeError: Cannot read property 'props' of undefined
...and the debugger pauses at the marked line (see code). When I mouseover this in {this.props.author}, I get a preview of the object which has the props property and everything...
Use function declaration ( render() {} or render: function {}) instead of arrow function render: () => {}
var Comment = React.createClass({
rawMarkup() {
var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
return {__html: rawMarkup};
},
render() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHtml={this.rawMarkup} />
</div>
);
}
});
Example
An arrow function expression has a shorter syntax compared to function expressions and lexically binds the this value (does not bind its own this, arguments, super, or new.target).
Arrow functions are always anonymous.
I had the same error message:
Cannot read property 'props' of undefined
...but from a different cause: when this is called from within a function, javascript can not reach the variable because this is in an outer scope. (Note: I was in ES5)
In this case, simply store this in another variable, prior to the function (in the scope of your component): var that = this;
Then you will be able to call that.props from within the function.
Hope this helps for other people who had that error message.
Detailed example below:
render: function() {
var steps = [];
var that = this; // store the reference for later use
var count = 0;
this.props.steps.forEach(function(step) {
steps.push(<Step myFunction={function(){that.props.anotherFunction(count)}}/>); // here you are
count += 1;
});
return (
<div>{steps}</div>
)
}
A little late post/answer.
Try to bind your function inside the constructor
example:
this.yourfunction = this.yourfunction.bind(this);
I am on ES6 and the arrow function did the trick: rawMarkup = () => {}
I have a link to return home and within that there is a button to remove an item from an array. To prevent the link to redirect to the home screen and to just remove the item from the array I am need to use ev.preventDefault().
Is it possible to pass an ev to a react method without using an arrow function in the render method? From my research and specifically the answer here it appears that the following is the only way to do so.
I am concerned that the arrow function causes a re-render every time, since new function is created on each render.
removeItem(label, e) {
e.preventDefault();
this.props.removeItem(label.id);
}
render () {
const { label } = this.props;
return (
<Link to'/'>
<span>Go Home</span>
<span onClick={(e) => this.removeItem(label, e) }> Click me <span>
</Link>
);
}
You could just remove the label parameter and get label from this.props directly. Refactor your app like this:
removeItem(ev) {
ev.preventDefault();
this.props.removeItem(this.props.label.id);
}
render () {
const { label } = this.props;
return (
<Link to'/'>
<span>Go Home</span>
<span onClick={this.removeItem}> Click me <span>
</Link>
);
}
This way, React will automatically pass the click event to your removeItem method.
Although, it should be said, that creating a new function on every render, probably isn't all that expensive.
In the light of avoiding to re-create the function you could extract the parameters into the class props. Here are some examples
For example this will create a new function all the time.
var List = React.createClass({
render() {
return (
<ul>
{this.props.items.map(item =>
<li key={item.id} onClick={this.props.onItemClick.bind(null, item.id)}>
...
</li>
)}
</ul>
);
}
});
Now it will not re-create the functions on re-rendering.
var List = React.createClass({
render() {
return (
<ul>
{this.props.items.map(item =>
<ListItem key={item.id} item={item} onItemClick={this.props.onItemClick} />
)}
</ul>
);
}
});
var ListItem = React.createClass({
render() {
return (
<li onClick={this._onClick}>
...
</li>
);
},
_onClick() {
this.props.onItemClick(this.props.item.id);
}
});
Here is some SO explanation React js onClick can't pass value to method
I have 2 files:
grid-body.jsx (GridBody) and grid-row.jsx (GridRow)
In GridBody, I declared a function showAlert which I pass to every GridRow:
var GridBody = React.createClass({
showAlert: function(msg) {
alert(msg);
},
render: function() {
var rows = this.props.rows.map(function(li) {
return (
<GridRow showAlert={this.showAlert} />
);
});
return (
<div>
{rows}
</div>
);
}
});
And in GridRow:
var GridRow = React.createClass({
toggle: function() {
this.props.showAlert('HEY'); // -----> ERROR - not a function
},
render: function() {
<div>
<a href="#" onClick={this.toggle} />
</div>
}
});
I'm trying to call the showAlert from parent and based on the examples I've seen, this is how to do it but I can't make it work.
you're using the wrong value for this inside of GridView.render. Either pass it explicitly to Array.map() (see the docs for how to do that) or assign this to some new variable at the very top of render() and reference that instead.
Here is a really, really great SO comment as to why this happens, as well as some other alternative workarounds if neither of the above work for you.
The context of the function passed to map in render method of GridBody is window and not the component. You can bind the interatee to get the behavior you want:
render: function() {
var rows = this.props.rows.map(function(li) {
return (
<GridRow showAlert={this.showAlert} />
);
}.bind(this));
return (
<div>
{rows}
</div>
);
}
I am not even sure where the issue is, the only thing I can think of is due to using .map() this is getting lost in translation. What I have below works as I could hope overall until I start trying to add in the onClick which gives me the error in the subject of this post. I need a pair of eyes to take a look and tell me what i did where I went wrong here.
var myApp = React.createClass({
mixins: [Backbone.React.Component.mixin],
_myPrivateFunction: function (objId) {
console.log(objId);
},
render: function () {
return (<ul className="list-group">
{this.state.collection.map(function (system) {
return (<li className="list-group-item" onClick={this._myPrivateFunction.bind(this, system.objId)}>{system.objName}</li>);
})}
</ul>);
}
});
Try
var myApp = React.createClass({
mixins: [Backbone.React.Component.mixin],
_myPrivateFunction: function (objId) {
console.log(objId);
},
render: function () {
var that = this;
return (<ul className="list-group">
{this.state.collection.map(function (system) {
return (<li className="list-group-item" onClick={that._myPrivateFunction.bind(that, system.objId)}>{system.objName}</li>);
})}
</ul>);
}
});
The scope of this is probably changing inside that function/event.
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".