In a React JS component I am rendering a list of items (Recipes), using JS map function from an array, passed in from a App parent component. Each item has a sub list (Ingredients), again rendered using map function.
What I want is to show/hide the sub list of Ingredients when you click on the Recipe title. I use a onClick function on the title that sets the CSS to display none or block, but I get the following error:
Uncaught TypeError: Cannot read property 'openRecipe' of undefined
Here is my code:
var App = React.createClass({
getInitialState(){
return{
showModal:false,
recipeKeys: [ ],
recipes: [ ]
}
},
addRecipeKey: function(recipe){
var allKeys = this.state.recipeKeys.slice();
var allRecipes = this.state.recipes.slice();
allKeys.push(recipe.name);
allRecipes.push(recipe);
localStorage.setObj("recipeKeys", allKeys);
this.setState({recipeKeys: allKeys, recipes: allRecipes}, function(){
console.log(this.state);
});
},
componentDidMount: function(){
var dummyRecipes = [
{
"name": "Pizza",
"ingredients": ["Dough", "Tomato", "Cheese"]
},
{
"name": "Sushi",
"ingredients": ["Rice", "Seaweed", "Tuna"]
}
]
if(localStorage.getItem("recipeKeys") === null){
localStorage.setObj("recipeKeys", ["Pizza", "Sushi"]);
dummyRecipes.forEach(function(item){
localStorage.setObj(item.name, item);
});
this.setState({recipeKeys: ["Pizza", "Sushi"], recipes: dummyRecipes}, function(){
console.log(this.state);
});
} else {
var recipeKeys = localStorage.getObj("recipeKeys");
var recipes = [];
recipeKeys.forEach(function(item){
var recipeObject = localStorage.getObj(item);
recipes.push(recipeObject);
});
this.setState({recipeKeys: recipeKeys, recipes: recipes}, function(){
console.log(this.state);
});
}
},
open: function(){
this.setState({showModal:true});
},
close: function(){
this.setState({showModal:false});
},
render: function(){
return(
<div className="container">
<h1>Recipe Box</h1>
<RecipeList recipes = {this.state.recipes} />
<AddRecipeButton openModal = {this.open}/>
<AddRecipe closeModal = {this.close} showModal={this.state.showModal} addRecipeKey = {this.addRecipeKey}/>
</div>
)
}
});
var RecipeList = React.createClass({
openRecipe: function(item){
var listItem = document.getElementById(item);
if(listItem.style.display == "none"){
listItem.style.display = "block";
} else {
listItem.style.display = "none";
}
},
render: function(){
return (
<ul className="list-group">
{this.props.recipes.map(
function(item,index){
return (
<li className="list-group-item" onClick={this.openRecipe(item)}>
<h4>{item.name}</h4>
<h5 className="text-center">Ingredients</h5>
<hr/>
<ul className="list-group" id={index} >
{item.ingredients.map(function(item){
return (
<li className="list-group-item">
<p>{item}</p>
</li>
)
})}
</ul>
</li>
)
}
)
}
</ul>
)
}
});
ReactDOM.render(<App />, document.getElementById('app'));
Also, I am trying to use a CSS method here, but maybe there is a better way to do it with React?
Can anyone help me? Thanks!
your issue is you are losing your this context in your map... you need to add .bind(this) to the end of your map function
{this.props.recipes.map(function(item,index){...}.bind(this))};
I answered another question very similar to this here. If you can use arrow functions it auto binds for you which is best. If you can't do that then either use a bind or make a shadow variable of your this context that you use inside the map function.
Now for the cleanup part. You need to clean up your code a bit.
var RecipeList = React.createClass({
getInitialState: function() {
return {display: []};
},
toggleRecipie: function(index){
var inArray = this.state.display.indexOf(index) !== -1;
var newState = [];
if (inArray) { // hiding an item
newState = this.state.display.filter(function(item){return item !== index});
} else { // displaying an item
newState = newState.concat(this.state.display, [index]);
}
this.setState({display: newState});
},
render: function(){
return (
<ul className="list-group">
{this.props.recipes.map(function(item,index){
var inArray = this.state.display.indexOf(index) !== -1;
return (
<li className="list-group-item" onClick={this.toggleRecipie.bind(this, index)}>
<h4>{item.name}</h4>
<h5 className="text-center">Ingredients</h5>
<hr/>
<ul className="list-group" id={index} style={{display: inArray ? 'block' : 'none'}} >
{item.ingredients.map(function(item){
return (
<li className="list-group-item">
<p>{item}</p>
</li>
)
}.bind(this))}
</ul>
</li>
)
}.bind(this))
}
</ul>
)
}
});
This may be a little complicated and you may not want to manage a list of indicies to toggle a view of ingredients. I'd recommend you make components for your code, this way its more react centric and it makes toggling a view much easier.
I'm going to write this in ES6 syntax also as you should be using ES6.
const RecipieList = (props) => {
return (
<ul className="list-group">
{props.recipes.map( (item,index) => <RecipieItem recipie={item} /> )
</ul>
);
};
class RecipieItem extends React.Component {
constructor(){
super();
this.state = {displayIngredients: false}
}
toggleRecipie = () => {
this.setState({displayIngredients: !this.state.displayIngredients});
}
render() {
return (
<li className="list-group-item" onClick={this.toggleRecipie}>
<h4>{item.name}</h4>
<h5 className="text-center">Ingredients</h5>
<hr/>
<ul className="list-group" style={{display: this.state.displayIngredients ? 'block' : 'none'}} >
{this.props.recipie.ingredients.map( (item) => <IngredientItem ingredient={item} /> )}
</ul>
</li>
);
}
}
const IngredientItem = (props) => {
return (
<li className="list-group-item">
<p>{props.ingredient}</p>
</li>
);
};
You also can use something like this:
render: function(){
var self = this;
return (
<ul className="list-group">
{this.props.recipes.map(
function(item,index){
return (
<li className="list-group-item" onClick={self.openRecipe(item)}>.....
Related
I know how to run loops inside react but how do I do it inside an object which is already inside an array being looped?
I am trying to display each ingredient item as an <li>, so far I have got it working with recipe but I am lost with ingredient. If anyone could chime in, I'd appreciate it.
var Recipes = React.createClass({
// hook up data model
getInitialState: function() {
return {
recipeList: [
{recipe: 'Cookies', ingredients: ['Flour ', 'Chocolate']},
{recipe: 'Cake', ingredients: ['Flour ', 'Sprinkles']},
{recipe: 'Onion Pie', ingredients: ['Onion ', 'Pie-Crust']}
]
}
},
loop: function() {
{this.state.recipeList.flatMap('ingredients').map(item, index) => (
<li key={index} className="list-group-item">{ingredient.ingredients}</li>
)}
},
render: function() {
return (
<div>
{this.state.recipeList.map((item, index) => (
<div className="panel panel-default">
<div className="panel-heading"><h3 className="panel-title">{item.recipe}</h3></div>
<div className="panel-body">
<ul className="list-group">
{this.loop}
</ul>
</div>
</div>
)
)}
</div>
);
}
});
How about this way :
loop: function(ingredients) {
return ingredients.map((ingredient, index) => {
return (<li key={index} className="list-group-item">{ingredient}</li>)
})
},
render(){
...
{this.loop(item.ingredients)}
...
}
One more thing, you shouldn't use index of array as key because it will be difficult to manage when editting the array later. It will be better if you assign key with something very unique like id or index + Date.now()
You seem to be missing a return statement in the loop method.
You can cascade rendering as deep as you'd wish, only remember that you need to call the method instead of just placing it in the component structure (see this.loop without call parentheses in your sample):
var myComponent = React.createClass({
renderListElements: function (parent) {
return this.state.listElements[parent].map((element, index) => (
<li
className="my-component__sub-component__list-element"
key={`${parent.uid}_${element.uid}`}
>
{element.name}
</li>
));
},
render: function () {
var parentsId = [ 0, 1, 2, 3 ];
return (
<div className="my-component">
{parentsId.map((item, index) => (
<div
className="my-component__sub-component"
key={item.uid}
>
{this.renderListElements(item)}
</div>
)}
<div/>
);
}
});
I have created InputField and button as separate component and make use of in the two js files incomeFields and Emifields. Then the two Js file as a component called in mail file called HomeLoanEmiCalculator. Then another file success file to calculate based on the input provided. on click of next button the value will be save in one object and we can retreive the entered value inside the success file to calculate the EMI values. I refer the site below https://www.online.citibank.co.in/products-services/loans/pop-up/home-loan-eligibility-calculator.htm
as we have to create in ReactJS. Below code not working properly. Even am using ref i cannot able to access the fieldvalues
var React = require('react');
var InputField = React.createClass({
getInitialState: function(){
return{
value: this.props.value || '',
};
},
setValue: function (event) {
this.setState({
value: event.currentTarget.value
});
setDefaultValue = this.props.fieldValues+"."+this.props.stateId;
},
render: function() {
return (<div>
<div>
<input className="inputText" id={this.props.stateId} type="text"
ref={this.props.stateId} onChange={this.props.setValue} defaultValue={this.props.setDefaultValue}/>
</div>);
}
});
module.exports = InputField;
var React = require('react')
var InputField = require('./InputField')
var IncomeFields = React.createClass({
nextStep: function(e) {
e.preventDefault();
var data = {
monthlyIncome : this.refs.monthlyIncome.getDOMNode().value,
rentalIncome : this.refs.rentalIncome.getDOMNode().value,
otherIncome : this.refs.otherIncome.getDOMNode().value
}
this.props.saveValues(data);
this.props.nextStep();
},
render: function() {
return (<div>
<h2>Please enter your income details</h2>
<ul className="inputList">
<li className="width25 hlec">
<InputField name="Gross Monthly Income"
stateId="monthlyIncome"
metrics= "INR"
required="true"
setDefaultValue={this.props.fieldValues.monthlyIncome}
imgPath="images/icons/icon_rupee.png"/>
</li>
<li className="width25 hlec">
<InputField name="Rental Income"
stateId="rentalIncome"
metrics= "INR"
setDefaultValue={this.props.fieldValues.rentalIncome}
imgPath="images/icons/icon_house.png"/>
</li>
<li className="width25 hlec last">
<InputField name="Other Income"
stateId="otherIncome"
metrics= "INR"
setDefaultValue={this.props.fieldValues.otherIncome}
imgPath="images/icons/icon_cashBundle.png"/>
</li>
</ul>
</div>
)
}
})
module.exports = IncomeFields
var React = require('react')
var InputField = require('./InputField')
var EmiFields = React.createClass({
nextStep: function(e) {
e.preventDefault();
var data = {
mortageLoan : this.refs.mortageLoan.getDOMNode().value,
persoanlLoan : this.refs.persoanlLoan.getDOMNode().value,
creditLoan : this.refs.creditLoan.getDOMNode().value,
autoLoan : this.refs.autoLoan.getDOMNode().value,
outstandingCCAmount : this.refs.outstandingCCAmount.getDOMNode().value,
interestRate : this.refs.interestRate.getDOMNode().value
}
this.props.saveValues(data);
this.props.nextStep();
},
render: function() {
return (<div>
<h2>Please enter your income details</h2>
<ul className="inputList">
<li className="width25 hlec">
<InputField name="Any other Mortgage Loan"
stateId="mortageLoan"
metrics= "INR"
imgPath="images/icons/icon_house.png"/>
</li>
<li className="width25 hlec">
<InputField name="Personal Loan"
stateId="personalLoan"
metrics= "INR"
imgPath="images/icons/icon_user.png"/>
</li>
<li className="width25 hlec">
<InputField name="Loan on Credit Card"
stateId="creditLoan"
metrics= "INR"
imgPath="images/icons/icon_card.png"/>
</li>
<li className="width25 hlec last">
<InputField name="Auto Loan"
stateId="autoLoan"
metrics= "INR"
imgPath="images/icons/icon_car.png"/>
</li>
</ul>
<ul className="inputList part2">
<li className="width25 hlec">
<InputField name="Outstanding Amount on Credit Card"
stateId="outstandingCCAmount"
metrics= "INR"
imgPath="images/icons/icon_rupee.png"/>
</li>
<li className="width25 hlec last">
<InputField name="Auto Loan"
stateId="otherLoan"
metrics= "INR"
imgPath="images/icons/icon_rupee.png"/>
</li>
</ul>
</div>
)
}
})
module.exports = EmiFields
var React = require('react');
var EmiCalculations = require('./store/EmiCalculator');
var aboutLoanStyle = {
width: '235px',
marginRight: '10px'
};
var loanAvail = null;
var homeValue = null;
var monthlyEMI = null;
var Success = React.createClass({
render: function() {
return (
<div> {this.calculate}
<div className="section1 outputSection">
<ul className="outputRack">
<li className="c2">
<div className="outputLabel">
<strong>Maximum Home Loan available to you</strong>
</div>
<div className="outputValue last" id="loanAvail" ref="loanAvail" defaultValue={this.props.fieldValues.loanAvail}>{EmiCalculations.getLoanAvail(this.props.fieldValues)}</div>
</li>
<li className="c2 last">
<div className="outputLabel">
<strong>Value of Home you can purchase</strong>
</div>
<div className="outputValue last" id="homevalue" ref="homeValue" defaultValue={this.props.fieldValues.homeValue}>{EmiCalculations.getHomeValue(this.props.fieldValues)}</div>
</li>
</ul>
<ul className="outputRack rack2">
<li className="c2">
<div className="outputLabel">
<strong>Your Monthly EMI</strong>
</div>
<div className="outputValue last" id="monthlyEMI" ref="monthlyEMI" defaultValue={this.props.fieldValues.monthlyEMI}>{EmiCalculations.getMonthlyEMI(this.props.fieldValues)}</div>
</li>
</ul>
</div>
</div>
)
}
})
module.exports = Success
var React = require('react')
var IncomeFields = require('./IncomeFields')
var aboutLoanStyle = {
width: '235px',
marginRight: '10px'
};
var Navigation = React.createClass({
getInitialState: function() {
return {
nextCount: 1
}
},
nextStep: function(e) {
e.preventDefault();
var cnt = ++this.state.nextCount
this.props.nextStep(cnt);
this.setState({nextCount: cnt});
console.log(IncomeFields.props.fieldValues);
},
render: function() {
return (<div className="inputButtonSection">
<div className="right step1">
<button className="blueBtn" style={aboutLoanStyle}>KNOW MORE ABOUT HOME LOANS</button>
{this.props.nextBtnVisibility ? <button key={this.state.showEmiField} className="blueBtn nextBtn" style={this.props.btnStyle} onClick={this.nextStep}>{this.props.nextStepLabel}</button> : null}
{this.props.resetBtnVisibility ? <button className="greyBtn reset first" onClick={this.resetValues}>RESET</button> : null }
</div>
</div>
)
}
})
module.exports = Navigation
var React = require('react')
var IncomeFields = require('./IncomeFields')
var EmiFields = require('./EmiFields')
var Success = require('./Success')
var assign = require('object-assign')
var Navigation = require('./Navigation')
var fieldValues = {
principalAmount : 100000,
monthlyIncome: null,
rentalIncome : null,
otherIncome : null,
mortageLoan : null,
persoanlLoan : null,
creditLoan : null,
autoLoan : null,
outstandingCCAmount : null,
otherLoan : null,
downPayment : null,
loanTenure : null,
loanAvail: null,
homeValue: null,
monthlyEMI: null
};
var HomeLoanEMICalculator = React.createClass({
getInitialState: function() {
return {
nextStepCount: 1,
nextStepLabel: "NEXT",
showEmiField: false,
showTenureFields: false,
showOutput: false,
nextBtnVisibility: true,
resetBtnVisibility: false,
btnStyle : {
marginRight: '10px'
}
}
},
saveValues: function(field_value) {
return function() {
fieldValues = assign({}, fieldValues, field_value)
}.bind(this)()
},
nextStep: function(count) {
//this.setState({nextStepCount: count});
this.showNext(count, true);
},
showNext: function(c, bool) {
if(c===2) {
this.setState({resetBtnVisibility : bool});
this.setState({showEmiField: bool});
} else if(c===3) {
this.setState({showTenureFields: bool});
this.setState({nextStepLabel: "CALCULATE"});
btnStyles = {
width: '110px',
marginRight: '10px'
}
this.setState({btnStyle: btnStyles});
} else if(c===4) {
this.setState({showOutput: bool});
this.setState({nextBtnVisibility: false});
}
},
render: function() {
return (
<div className="calculatorWrapper">
<IncomeFields fieldValues={fieldValues}
nextStep={Navigation.nextStep}
saveValues={this.saveValues}/>
{this.state.showEmiField ? <EmiFields fieldValues={fieldValues}
nextStep={Navigation.nextStep}
saveValues={this.saveValues}/>: null}
{this.state.showOutput ? <Success fieldValues={fieldValues}/> : null}
<Navigation nextBtnVisibility={this.state.nextBtnVisibility} resetBtnVisibility={this.state.resetBtnVisibility} btnStyle={this.state.btnStyle} nextStepLabel={this.state.nextStepLabel} nextStep={this.nextStep}/>
</div>
)
}
})
module.exports = HomeLoanEMICalculator
var React = require('react')
var ReactDOM = require('react-dom')
var HomeLoanEMICalculator = require('./components/HomeLoanEMICalculator')
window.onload = function() {
ReactDOM.render(
<HomeLoanEMICalculator />,
document.getElementById('emi-calc-form')
)
}
Please help me to do this. pls thanks in advance
There are a few problems with what you are trying to do:
If you read the documentation on refs:
https://facebook.github.io/react/docs/more-about-refs.html#the-ref-string-attribute
You will see that you need to assign a ref attribute on the component you want to get the DOM value for.
this.refs.monthlyIncome.getDOMNode().value
only works if you do something like:
<input className="inputText" type="text" ref="monthlyIncome" />
One way of tackling this is to have the fields be properties in a state, and based on whether those states are set, trigger your form to move into it's next state. You probabl
The code may resemble something like below. You still need to implement functions so that onChange will update the state. You can easily replace input with InputField if input field takes the value via props, and the onChange function comes back up to change the state.
var InputField = React.createClass({
getInitialState: function(){
return {mortgageLoan: null, principalAmount: null};
},
renderFirstForm: function() {
return (
<div>Mortgage Loan: <input className="inputText" value={this.state.mortgageLoan} /></div>
)
},
firstFormComplete: function() {
return (this.state.mortgageLoan != null);
}
renderSecondForm: function() {
return (
<div>Principal Amount: <input className="inputText" value={this.state.principalAmount} onChange={} /></div>
)
}
})
Here, i have included a my example code. If it is one dimensional array means, i can easily insert json data's into my code. How to achieve this one with multidimensional json data with react js?
var Category = React.createClass({
render: function() {
return (
<div>
{this.props.data.map(function(el,i) {
return <div key={i}>
<div>
{el.product}
</div>
<div>
{el.quantity}
</div>
</div>;
})}
</div>
);
}
});
var data = [
{
product:"a",
quantity:28,
sub:[
{
subItem:'a'
},
{
subItem:'b'
}
]
},
{
product:"b",
quantity:20,
sub:[
{
subItem:'a'
},
{
subItem:'b'
}
]
}
];
React.render(<Category data={data}/>, document.body);
You can create component for sub categories like this,
var SubCategory = React.createClass({
render: function () {
var list = this.props.data.map(function(el, i) {
return <li key={i}>{ el.subItem }</li>;
});
return <ul>{ list }</ul>;
}
});
and use it in Category component
{this.props.data.map(function(el,i) {
return <div key={i}>
<div>{el.product}</div>
<div>{el.quantity}</div>
<SubCategory data={ el.sub } />
</div>;
})}
Example
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 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.