I'm building an app in React based on this tutorial.
Instead of using the updated es2016, I'm using an older way, so I'm having some trouble with the challenges that come. I got this error in the browser: "TypeError: Cannot read property 'props' of undefined". I assume it's pointing to the {this.props.onDelete} part. Here's a snippet of my code for the Notes.jsx component:
var Notes = React.createClass({
render: function () {
return (
<ul>
{this.props.notes.map(
function(note) {
return (
<li key={note.id}>
<Note
onTheDelete={this.props.onDelete}
task={note.task} />
</li>
);
}
)}
</ul>
);
}
});
And here's a snippet from App.jsx, it's parent:
var App = React.createClass({
getInitialState: function () {
return {
notes: [
{
id: uuid.v4(),
task: 'Learn React'
},
{
id: uuid.v4(),
task: 'Do laundry'
}
]
}
},
newNote: function () {
this.setState({
notes: this.state.notes.concat([{
id: uuid.v4(),
task: 'New task'
}])
});
},
deleteNote: function() {
return 'hi';
},
render: function () {
var {notes} = this.state;
return (
<div>
<button onClick={this.newNote}>+</button>
<Notes notes={notes} onDelete={this.deleteNote}/>
</div>
);
}
});
I deleted the actually useful parts from deleteNote to make sure no issues were there. I'm having a difficult time wrapping my head around using "this" and what the binding is doing in the tutorial I mentioned.
this inside the map function isn't the same as this outside of it because of how JS works.
You can save off this.props.onDelete and use it w/o the props reference:
render: function () {
var onDelete = this.props.onDelete;
return (
<ul>
{this.props.notes.map(
function(note) {
return (
<li key={note.id}>
<Note
onTheDelete={onDelete}
task={note.task}
/>
</li>
);
}
)}
</ul>
);
}
Unrelated, but I'd move that map function into its own function and avoid the deep nesting.
Dave Newton's answer is entirely correct, but I just wanted to add that if you use ES6 arrow functions then you can avoid having to keep an additional reference to this, as well as removing the return statement and taking advantage of the implicit return syntax.
var Notes = React.createClass({
render: function () {
return (
<ul>
{this.props.notes.map(
note => {(
<li key={note.id}>
<Note
onTheDelete={this.props.onDelete}
task={note.task} />
</li>
)}
)}
</ul>
);
}
});
Related
This question already has answers here:
Applying className/onClick/onChange not working on Custom React Component
(2 answers)
Closed 5 years ago.
I'm learning React, I have 3 components:
App, contains UserList
UserList, contains a list of cards
UserCard, the content of the previous component
Here's my UserList code:
state = {
users: [
{ id: 1, name: 'Chris', age: 20 },
{ id: 2, name: 'Max', age: 1 },
{ id: 3, name: 'Jean', age: 23 },
{ id: 4, name: 'Luc', age: 30 }
]
}
userList = this.state.users.map(user => {
return (
<div key={user.id} className="column">
<UserCard name={user.name} age={user.age} onClick={this.userClickHandler} />
</div>);
});
userClickHandler = () => {
console.log('clicked !');
};
render() {
return (
<div className="columns">
{this.userList}
</div>);
}
For some odd reason, my userClickHandler does not get triggered when a UserCard is clicked.
Note that I have tried the following:
Changing onClick={this.userClickHandler} to onClick= () => {this.userClickHandler} and that it works when I move the code of my userList in the render method without assigning it to a variable like so:
<div className="columns">
{ this.state.users.map(user => {
return (
<div key={user.id} className="column" onClick={this.userClickHandler}>
<UserCard name={user.name} age={user.age} />
</div>);
}) }
</div>);
What's the apparent problem?
You need to bind the function to the scope here..Sorry about my previous answer. I didn't analyse it properly
<div key={user.id} className="column" onClick={this.userClickHandler.bind(this)}>
Normally, I'd store something like this in a variable in the render section like this. Let me know if this helps. :
render() {
let userList = this.state.users.map(user => {
return (
<div key={user.id} className="column">
<UserCard name={user.name} age={user.age} onClick={this.userClickHandler} />
</div>);
});
return (
<div className="columns">
{userList}
</div>);
}
Also, in the constructor of userList, bind the click handler:
constructor(){
this.userClickHandler = this.userClickHandler.bind(this);
}
This could likely be that you've discovered function hoisting.
Arrow functions are not hoisted.
You are only able to make it invoke it after you've declared it.
A normal function using a function keyword can be invoked at a line number before it is declared.
https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
I am building an apartment renting app in React, with very limited Javascript and programmatic knowledge (using this app to learn while i go). I've stumbled upon a big roadblock trying to build the filter functionality.
Here is the code for the FilterContainer.js container component
const FilterContainer = React.createClass({
render () {
return (
<section id="filterContainer" className="container">
<Filter
name={'Kvart'}
filterList={['Šubićevac', 'Meterize', 'Baldekin', 'Vidici', 'Rokići', 'Grad']} />
<Filter
name={'Cijena'}
filterList={['< 600kn', '600kn - 700kn', '700kn - 800kn', '800kn - 900kn', '> 900kn']} />
<Filter
name={'Broj osoba'}
filterList={['1', '2', '3', '4', '5', '6']} />
<Filter
name={'Kvadratura'}
filterList={['< 40m', '40m - 50m', '50m - 60m', '60m - 70m', '70m - 80m', '> 80m']} />
</section>
)
}});
This is the Filter.js component
var Filter = React.createClass({
getDefaultProps() {
return {
filterList: [],
name: ''
};
},
render() {
var handleClick = function(i, props) {
console.log('You clicked: ' + props.filterList[i].listValue[i]);
}
return (
<div className="filterCloud quarter-section">
<h3>{this.props.name}</h3>
<ul>
{this.props.filterList.map(function(listValue, i, props) {
return <li onClick={handleClick.bind(this, i, props)} key={i}>{listValue}</li>;
}, this)} {/* this at the end to fix the scope issue from global to local */}
</ul>
</div>
)
}});
This is how the Filter looks on the website atm
The error I am getting is "Filter.js:13 Uncaught TypeError: Cannot read property '0' of undefined" whenever i click on one of the filter items, I want to be able to save the name of the filter item as data which I will use to filter the apartments.
So the simple issue is the undefined error. The more complex issue i have is the fact that I have no idea how to build the filter functionality in my app, so if any of you guys could point me in the right direction it would help me alot.
The third parameter to the map function in javascript is the full array that is being mapped over.
In this case, it is the filterList, not this.props.
So in you click handler, you don't need to traverse props:
var handleClick = function(i, props) {
console.log('You clicked: ' + props[i].listValue[i]);
}
It would be easier to see if you did some renaming:
render() {
var handleClick = function(i, filterList) {
console.log('You clicked: ' + filterList[i].listValue[i]);
}
return (
<div className="filterCloud quarter-section">
<h3>{this.props.name}</h3>
<ul>
{this.props.filterList.map(function(listValue, i, filterList) {
return <li onClick={handleClick.bind(this, i, filterList)} key={i}>{listValue}</li>;
}, this)} {/* this at the end to fix the scope issue from global to local */}
</ul>
</div>
)
}});
I am facing a very similar problem to this question, but I am fetching data using a Promise and want to render it into the DOM when it comes through. The console.log() displays all the items correctly. I think my problem is that the lodash.map returns an array of <li> elements, and so I am trying to call this.renderItems() in order to render (but renderItems() doesn't seem to exist). Am I doing something unconventional, is there an easier way, is there an equivalent function to replace my renderItems()?
renderArticleHeadline: function(article) {
console.log('renderArticleHeadline', article.headline);
return (
<li>
{article.headline}
</li>
)
},
render: function() {
return (
<div>
<ul>
{
this.renderItems(
this.fetchFrontPageArticles().then(data => {
lodash.map(data, this.renderArticleHeadline)
})
)
}
</ul>
</div>
);
}
It should be something like this
getInitialState: function() {
return {
items: []
};
},
renderArticleHeadline: function(article) {
return (
<li>
{article.headline}
</li>
);
},
componentDidMount: function() {
this.fetchFrontPageArticles().then(data => {
this.setState({
items: data
});
});
},
render: function() {
var items = lodash.map(this.state.items, this.renderArticleHeadline);
return (
<div>
<ul>
{items}
</ul>
</div>
);
}
P.S. read thinking in react
I'm spending time on something probably simple:
I'd like to implement a search bar, ideally updating the list of item as-you-type. My small app uses React and Backbone (for models and collections).
Displaying the list isn't too hard, it all works perfectly doing this (the mixin i'm using basically allows easy collections retrieval):
var List = React.createClass ({
mixins: [Backbone.React.Component.mixin],
searchFilter: function () {
//some filtering code here, not sure how (filter method is only for arrays...)
}
}
getInitialState: function () {
initialState = this.getCollection().map(function(model) {
return {
id: model.cid,
name: model.get('name'),
description: model.get('description')
}
});
return {
init: initialState,
items : []
}
},
componentWillMount: function () {
this.setState({items: this.state.init})
},
render: function(){
var list = this.state.items.map(function(obj){
return (
<div key={obj.id}>
<h2>{obj.name}</h2>
<p>{obj.description}</p>
</div>
)
});
return (
<div className='list'>
{list}
</div>
)
}
});
Now i've tried with no success to first translate the backbone collection into "state" with the getInitialState method, my idea was to proxy through a copy of the collection, which then could hold the search results. I'm not showing here my attemps for the sake of clarity(edit: yes i am), could someone guide me to the right approach? Thanks in advance.
There are many ways to accomplish this, but the simplest (in my opinion) is to store your search criteria in the List component's state and use it to filter which items from your collection get displayed. You can use a Backbone collection's built in filter method to do this.
var List = React.createClass ({
mixins: [Backbone.React.Component.mixin],
getInitialState: function () {
return {
nameFilter: ''
};
},
updateSearch: function (event) {
this.setState({
nameFilter: event.target.value
});
},
filterItems: function (item) {
// if we have no filter, pass through
if (!this.state.nameFilter) return true;
return item.name.toLowerCase().indexOf(this.state.nameFilter) > -1;
},
render: function(){
var list = this.props.collection
.filter(this.filterItems.bind(this))
.map(function(obj){
return (
<div key={obj.id}>
<h2>{obj.name}</h2>
</div>
)
});
return (
<div className='list'>
{list}
<input onChange={this.updateSearch} type="text" value={this.state.nameFilter}/>
</div>
)
}
});
var collection = new Backbone.Collection([
{
name: 'Bob'
},
{
name: 'Bill'
},
{
name: 'James'
}
]);
React.render(<List collection={collection}/>, document.body);
jsbin
The search criteria could easily be passed down from a parent component as a prop, so the search input does not have to live inside your List component.
Eventually I also found a different solution (below), but it involves copying the entire collection into state, which is probably not such a good idea...
var List = React.createClass ({
mixins: [Backbone.React.Component.mixin],
searchFilter: function () {
var updatedlist = this.state.init;
var searchText = this.refs.searchbar.getDOMNode().value
updatedlist = updatedlist.filter(function (item) {
return item.name.toLowerCase().search(
searchText.toLowerCase()) !== -1
});
this.setState({items: updatedlist})
}
},
getInitialState: function () {
initialState = this.getCollection().map(function(model) {
return {
id: model.cid,
name: model.get('name'),
description: model.get('description')
}
});
return {
init: initialState,
items : []
}
},
componentWillMount: function () {
this.setState({items: this.state.init})
},
render: function(){
var list = this.state.items.map(function(obj){
return (
<div key={obj.id}>
<h2>{obj.name}</h2>
<p>{obj.description}</p>
</div>
)
});
return (
<div className='list'>
<input ref='searchbar' type="text" placeholder="Search" onChange={this.searchFilter}/>
{list}
</div>
)
}
});
I have
var TestApp = React.createClass({
getComponent: function(){
console.log(this.props);
},
render: function(){
return(
<div>
<ul>
<li onClick={this.getComponent}>Component 1</li>
</ul>
</div>
);
}
});
React.renderComponent(<TestApp />, document.body);
I want to color the background of the clicked list element. How can I do this in React ?
Something like
$('li').on('click', function(){
$(this).css({'background-color': '#ccc'});
});
Why not:
onItemClick: function (event) {
event.currentTarget.style.backgroundColor = '#ccc';
},
render: function() {
return (
<div>
<ul>
<li onClick={this.onItemClick}>Component 1</li>
</ul>
</div>
);
}
And if you want to be more React-ive about it, you might want to set the selected item as state of its containing React component, then reference that state to determine the item's color within render:
onItemClick: function (event) {
this.setState({ selectedItem: event.currentTarget.dataset.id });
//where 'id' = whatever suffix you give the data-* li attribute
},
render: function() {
return (
<div>
<ul>
<li onClick={this.onItemClick} data-id="1" className={this.state.selectedItem == 1 ? "on" : "off"}>Component 1</li>
<li onClick={this.onItemClick} data-id="2" className={this.state.selectedItem == 2 ? "on" : "off"}>Component 2</li>
<li onClick={this.onItemClick} data-id="3" className={this.state.selectedItem == 3 ? "on" : "off"}>Component 3</li>
</ul>
</div>
);
},
You'd want to put those <li>s into a loop, and you need to make the li.on and li.off styles set your background-color.
Two ways I can think of are
var TestApp = React.createClass({
getComponent: function(index) {
$(this.getDOMNode()).find('li:nth-child(' + index + ')').css({
'background-color': '#ccc'
});
},
render: function() {
return (
<div>
<ul>
<li onClick={this.getComponent.bind(this, 1)}>Component 1</li>
<li onClick={this.getComponent.bind(this, 2)}>Component 2</li>
<li onClick={this.getComponent.bind(this, 3)}>Component 3</li>
</ul>
</div>
);
}
});
React.renderComponent(<TestApp /> , document.getElementById('soln1'));
This is my personal favorite.
var ListItem = React.createClass({
getInitialState: function() {
return {
isSelected: false
};
},
handleClick: function() {
this.setState({
isSelected: true
})
},
render: function() {
var isSelected = this.state.isSelected;
var style = {
'background-color': ''
};
if (isSelected) {
style = {
'background-color': '#ccc'
};
}
return (
<li onClick={this.handleClick} style={style}>{this.props.content}</li>
);
}
});
var TestApp2 = React.createClass({
getComponent: function(index) {
$(this.getDOMNode()).find('li:nth-child(' + index + ')').css({
'background-color': '#ccc'
});
},
render: function() {
return (
<div>
<ul>
<ListItem content="Component 1" />
<ListItem content="Component 2" />
<ListItem content="Component 3" />
</ul>
</div>
);
}
});
React.renderComponent(<TestApp2 /> , document.getElementById('soln2'));
Here is a DEMO
I hope this helps.
Here is how you define a react onClick event handler, which was answering the question title... using es6 syntax
import React, { Component } from 'react';
export default class Test extends Component {
handleClick(e) {
e.preventDefault()
console.log(e.target)
}
render() {
return (
<a href='#' onClick={e => this.handleClick(e)}>click me</a>
)
}
}
Use ECMA2015. Arrow functions make "this" a lot more intuitive.
import React from 'react';
class TestApp extends React.Component {
getComponent(e, index) {
$(e.target).css({
'background-color': '#ccc'
});
}
render() {
return (
<div>
<ul>
<li onClick={(e) => this.getComponent(e, 1)}>Component 1</li>
<li onClick={(e) => this.getComponent(e, 2)}>Component 2</li>
<li onClick={(e) => this.getComponent(e, 3)}>Component 3</li>
</ul>
</div>
);
}
});
React.renderComponent(<TestApp /> , document.getElementById('soln1'));`
If you're using ES6, here's some simple example code:
import React from 'wherever_react_is';
class TestApp extends React.Component {
getComponent(event) {
console.log('li item clicked!');
event.currentTarget.style.backgroundColor = '#ccc';
}
render() {
return(
<div>
<ul>
<li onClick={this.getComponent.bind(this)}>Component 1</li>
</ul>
</div>
);
}
}
export default TestApp;
In ES6 class bodies, functions no longer require the 'function' keyword and they don't need to be separated by commas. You can also use the => syntax as well if you wish.
Here's an example with dynamically created elements:
import React from 'wherever_react_is';
class TestApp extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{name: 'Name 1', id: 123},
{name: 'Name 2', id: 456}
]
}
}
getComponent(event) {
console.log('li item clicked!');
event.currentTarget.style.backgroundColor = '#ccc';
}
render() {
<div>
<ul>
{this.state.data.map(d => {
return(
<li key={d.id} onClick={this.getComponent.bind(this)}>{d.name}</li>
)}
)}
</ul>
</div>
);
}
}
export default TestApp;
Note that each dynamically created element should have a unique reference 'key'.
Furthermore, if you would like to pass the actual data object (rather than the event) into your onClick function, you will need to pass that into your bind. For example:
New onClick function:
getComponent(object) {
console.log(object.name);
}
Passing in the data object:
{this.state.data.map(d => {
return(
<li key={d.id} onClick={this.getComponent.bind(this, d)}>{d.name}</li>
)}
)}
Handling events with React elements is very similar to handling events
on DOM elements. There are some syntactic differences:
React events are named using camelCase, rather than lowercase.
With JSX you pass a function as the event handler, rather than a string.
So as mentioned in React documentation, they quite similar to normal HTML when it comes to Event Handling, but event names in React using camelcase, because they are not really HTML, they are JavaScript, also, you pass the function while we passing function call in a string format for HTML, they are different, but the concepts are pretty similar...
Look at the example below, pay attention to the way event get passed to the function:
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
import React from 'react';
class MyComponent extends React.Component {
getComponent(event) {
event.target.style.backgroundColor = '#ccc';
// or you can write
//arguments[0].target.style.backgroundColor = '#ccc';
}
render() {
return(
<div>
<ul>
<li onClick={this.getComponent.bind(this)}>Component 1</li>
</ul>
</div>
);
}
}
export { MyComponent }; // use this to be possible in future imports with {} like: import {MyComponent} from './MyComponent'
export default MyComponent;
class FrontendSkillList extends React.Component {
constructor() {
super();
this.state = { selectedSkill: {} };
}
render() {
return (
<ul>
{this.props.skills.map((skill, i) => (
<li
className={
this.state.selectedSkill.id === skill.id ? "selected" : ""
}
onClick={this.selectSkill.bind(this, skill)}
style={{ cursor: "pointer" }}
key={skill.id}
>
{skill.name}
</li>
))}
</ul>
);
}
selectSkill(selected) {
if (selected.id !== this.state.selectedSkill.id) {
this.setState({ selectedSkill: selected });
} else {
this.setState({ selectedSkill: {} });
}
}
}
const data = [
{ id: "1", name: "HTML5" },
{ id: "2", name: "CSS3" },
{ id: "3", name: "ES6 & ES7" }
];
const element = (
<div>
<h1>Frontend Skill List</h1>
<FrontendSkillList skills={data} />
</div>
);
ReactDOM.render(element, document.getElementById("root"));
.selected {
background-color: rgba(217, 83, 79, 0.8);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
#user544079 Hope this demo can help :) I recommend changing background color by toggling classname.
import React from 'react';
class MyComponent extends React.Component {
getComponent(event) {
event.target.style.backgroundColor = '#ccc';
// or you can write
//arguments[0].target.style.backgroundColor = '#ccc';
}
render() {
return(
<div>
<ul>
<li onClick={this.getComponent.bind(this)}>Component 1</li>
</ul>
</div>
);
}
}
export { MyComponent }; // use this to be possible in future imports with {} like: import {MyComponent} from './MyComponent'
export default MyComponent;
You can make use of the React.createClone method. Create your element, than create a clone of it. During the clone's creation, you can inject props. Inject an onClick : method prop like this
{ onClick : () => this.changeColor(originalElement, index) }
the changeColor method will set the state with the duplicate, allowing you sto set the color in the process.
render()
{
return(
<ul>
{this.state.items.map((val, ind) => {
let item = <li key={ind}>{val}</li>;
let props = {
onClick: () => this.Click(item, ind),
key : ind,
ind
}
let clone = React.cloneElement(item, props, [val]);
return clone;
})}
</ul>
)
}
This is a non-standard (but not so uncommon) React pattern that doesn't use JSX, instead putting everything inline. Also, it's Coffeescript.
The 'React-way' to do this would be with the component's own state:
(c = console.log.bind console)
mock_items: [
{
name: 'item_a'
uid: shortid()
}
{
name: 'item_b'
uid: shortid()
}
{
name: 'item_c'
uid: shortid()
}
]
getInitialState: ->
lighted_item: null
render: ->
div null,
ul null,
for item, idx in #mock_items
uid = item.uid
li
key: uid
onClick: do (idx, uid) =>
(e) =>
# justf to illustrate these are bound in closure by the do lambda,
c idx
c uid
#setState
lighted_item: uid
style:
cursor: 'pointer'
background: do (uid) =>
c #state.lighted_item
c 'and uid', uid
if #state.lighted_item is uid then 'magenta' else 'chartreuse'
# background: 'chartreuse'
item.name
This example works -- I tested it locally.
You can check out this example code exactly at my github.
Originally the env was only local for my own whiteboard r&d purposes but I posted it to Github for this. It may get written over at some point but you can check out the commit from Sept 8, 2016 to see this.
More generally, if you want to see how this CS/no-JSX pattern for React works, check out some recent work here. It's possible I will have time to fully implement a POC for this app idea, the stack for which includes NodeJS, Primus, Redis, & React.