React js attach click event on every single span element - reactjs

I have the following react code at: http://codepen.io/AlexanderWeb00/pen/ZOWyNr
I am trying to attach for every span a click event that allows to console.log the correspondent number that you have clicked on:
var one = [],
two = [];
var SingleButton = React.createClass({
getInitialState: function() {
return {
val1: one,
val2: two,
action: ''
}
},
handleClick: function() {
var val1 = this.state.val1,
val2 = this.state.val2;
if(!$('.action').length){
val1.push(this.props.numbers);
console.log(val1[0].join(" "));
}
},
render: function() {
var numbers = [];
var classes = 's-btn large-4 columns';
var ff =this.handleClick;
this.props.numbers.forEach(function(el){
numbers.push(<span onClick={ff} className={classes}>{el}</span>);
});
return (
<div className="row">
{numbers}
</div>
);
}
});
what I am getting instead is 123456789 as opposed to 5 if I only click on number 5.

As stated in the comment, you are pushing the whole numbers arrays into val1, therefore it will always display the whole list. To solve it we need to pinpoint the clicked span, get its number, and add it to the list.
The first method would be using jQuery, and the eventArg that the onClick sends.
Once the span is clicked our handleClick is called, with the click event data as a parameter. We will extract the clicked span from the event arg, ie eventArg.target, and get its text using jQuery (ie $(eventArg.target).text()).
To specifically solve the issue you could:
var one = [],
two = [];
var SingleButton = React.createClass({
getInitialState: function() {
return {
val1: one,
val2: two,
action: ''
}
},
handleClick: function(e) {
var val1 = this.state.val1,
val2 = this.state.val2;
if(!$('.action').length){
val1.push($(e.target).text());
console.log(val1.join(" "));
}
},
render: function() {
var numbers = [];
var classes = 's-btn large-4 columns';
var ff =this.handleClick;
this.props.numbers.forEach(function(el){
numbers.push(<span onClick={ff} className={classes}>{el}</span>);
});
return (
<div className="row">
{numbers}
</div>
);
}
});
https://jsfiddle.net/omerts/zm0bzmwp/
Another option is to wrap the span's number in a colsure. In other words, create an anonymous function, which will have the number as part of its scope object. Since closures are out of the scope of this question, for further reading: Closures
var SingleButton = React.createClass({
getInitialState: function() {
return {
val1: [],
val2: [],
action: ''
}
},
handleClick: function(number) {
if(!$('.action').length){
const updatedList = this.state.val1.slice(); // clone the array, and add the new number
updatedList.push(number);
console.log(updatedList.join(" "));
this.setState({val1: updatedList});
}
},
render: function() {
var numbers = [];
var classes = 's-btn large-4 columns';
this.props.numbers.forEach(function(el){
numbers.push(<span onClick={() => {this.handleClick(el)}} className={classes}>{el}</span>);
}, this);
return (
<div className="row">
{numbers}
</div>
);
}
});
https://jsfiddle.net/omerts/590my903/
Second option is better, since you should not be mutating state, unless you are using this.setState. If you don't really need it as part of your state, just make val1, and val2 data memebers of your SingleButton.

Related

Event is not binding while creating dynamic controls

When I add an event using the first code snippet, the event is firing. But when i do the same thing with a variable, the event is not firing/binding. can any one help me?
var ProductTable = React.createClass({
ChangeSearch : function(event){console.log("Text changed");},
render: function() {
return (<input type="text"onChange= {this.ChangeSearch} />);
}
});
same code with variable:
var ProductTable = React.createClass({
var headerFilters =[];
ChangeSearch : function(event){console.log("Text changed");},
render: function() {
headerFilters.push(<th><input type="text" onChange={this.ChangeSearch} /></th>);
return ({headerFilters});
}
});
First one and the second one are looping through and adding the text boxes. With the variable only i will be able to generalize the code. I have removed the looping from the first code snippet to reduce the complexity.
If you need to render list of input elements its better to create simple element and map over it in Parent. Read react documentation about dynamic children
jssfidle:
code sample:
var InputTextElement = React.createClass({
changeHandler: function(event) {
console.log('text.changed' + event.target.value)
},
render: function() {
return (
<input type="text" name={this.props.name} onChange={this.changeHandler} />
)
}
})
var ProductTable = React.createClass({
render: function() {
var headerFilters =[{id: 1, name: "test"}, {id:2, name: "another"}];
return (
<div>
{ headerFilters.map(function(data) {
return <InputTextElement key={data.id} name={data.name} />;
})}
</div>
);
}
});
setTimeout(function() {
ReactDOM.render( <ProductTable />
, document.getElementById('element'))
} ,700);
working like a charm.

[] react jsx generates "a unique key prop" warning but it's good if I use ()

Can someone please clarify why this render function
var ListItemWrapper = React.createClass({
render: function() {
var renderMe = [
<span>{this.props.data.id}</span>,
<span>{this.props.data.text}</span>
];
return <div>{renderMe}</div>;
}
});
generates
Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of ListItemWrapper. See fb.me/react-warning-keys for more information.
and this one is good with no warnings:
var ListItemWrapper = React.createClass({
render: function() {
var renderMe = (
<p>
<span>{this.props.data.id}</span>
<span>{this.props.data.text}</span>
</p>
);
return <div>{renderMe}</div>;
}
});

How to Render Component Dynamically in React

I have developed a Component base application in which I have developed a different Component but at time of rendering I have to render based on user action currently what I have tried is mentioned below.
My Component looks like:
var React = require('react');
var Constants = require('../constants/ActionTypes');
var ItemRelationStore = require('../stores/ItemRelationStore');
var ItemStore=require('../stores/ItemStore');
var TreeView=require('./TreeView.react');
var childNodes={};
function getItemRelationState() {
return {
Items:JSON.parse(JSON.stringify(ItemStore.getItem())),
Items1:JSON.parse(JSON.stringify(ItemStore.getItem1()))
};
}
// Define main Controller View
var ItemPage = React.createClass({
// Get initial state from stores
getInitialState: function() {
return getItemRelationState();
},
// Add change listeners to stores
componentDidMount: function() {
ItemStore.CallItem("TenantId",this.props.MetaItemId,this.props.Key);
ItemStore.addChangeListener(this._onChange);
},
// Remove change listers from stores
componentWillUnmount: function() {
ItemStore.removeChangeListener(this._onChange);
},
// Render our child components, passing state via props
render: function() {
var itemslist;
if(this.props.Key==1)
{
itemslist=this.state.Items;
}
else
{
itemslist=this.state.Items1;
}
var metaid=this.props.MetaItemId;
childNodes= Object.keys(itemslist).map(function(item,index){
return (
{
title:itemslist[item].Name,
Id: itemslist[item].Id,
metaitemId:metaid
}
)
});
var tree= {
title: this.props.MetaItemName,
metaitemId:metaid,
childNodes: [
{childNodes}
]
};
return (
<div className="treeview">
<TreeView node={tree}/>
</div>
);
},
// Method to setState based upon Store changes
_onChange: function() {
this.setState(getItemRelationState());
}
});
module.exports = ItemPage;
Now when I used static in another component like
var Item=require('../../Item');
React.render(<Item MetaItemName={"Users"} MetaItemId={1} Key={1}/>,document.getElementById("firstCol"));
then it's working fine. but I want to render dynamically so I have prepared one CustomeComponent like
var React = require('react');
var Item=require('../Item');
var CustomComponent = React.createClass({
// Get initial state from stores
getInitialState: function() {
return null;
},
// Add change listeners to stores
componentDidMount: function() {
},
componentWillReceiveProps: function(nextProps) {
// Load new data when the dataSource property changes.
if (nextProps.dataSource != this.props.dataSource) {
this.loadData(nextProps.dataSource);
}
},
// Remove change listers from stores
componentWillUnmount: function() {
},
// Render our child components, passing state via props
render: function() {
var InputType=this.props.inputType;
console.log(InputType);
return (
<InputType/>
);
},
// Method to setState based upon Store changes
_onChange: function() {
}
});
module.exports = CustomComponent;
and I am calling this custom component like
React.render(<CustomComponent inputType="Item" />,document.getElementById("secondCol"));
but it's not working i mean to say nothing render on Page.
I just want to know how we can Achive dynamic rendering in React.
Thanks in Advance.
Maybe React.render(<CustomComponent inputType={Item} />,document.getElementById("secondCol"));?

$watch not finding changes on property in array of objects on new item in angular

I have an array of objects like this...
[{ name: 'foo', price: 9.99, qty: 1 }]
The UI allows for new items to be added to this array. I'm trying to listen for those new items to be added AND for changes on the qty property of each item.
$scope.order = [];
$scope.totalItems = 0;
$scope.$watchCollection('order', function() {
$scope.totalItems = $scope.order.reduce(function(memo, o) {
return memo + o.qty;
}, 0);
});
$scope.addItem = function() {
$scope.order.push({
name: $scope.item.name,
qty: 1,
options: []
});
};
As you can see from this... http://d.pr/v/MJzP The function fires when a new item is added, but NOT when the qty changes on that new item.
Maybe you should attach an ngChange to your quantity input box? Like this
<input ng-model="item.qty" ng-change="calculateTotal()" type="number"/>
Then in your controller:
$scope.calculateTotal = function() {
$scope.totalItems = $scope.order.reduce(function(memo, o) {
return memo + o.qty;
}, 0);
};
Then at the end of your add item function:
$scope.addItem = function() {
// Your logic
$scope.calculateTotal();
};
EDIT: After thinking about this more, the ngChange might automatically be invoked when the addItem function is called so calling it from addItem might not be entirely necessary

Single Controller for multiple html section and data from ajax request angularjs

I'm trying to show two section of my html page with same json data, i don't want to wrap both in same controller as it is positioned in different areas. I have implemented that concept successfully by using local json data in "angular service" see the demo
<div ng-app="testApp">
<div ng-controller="nameCtrl">
Add New
Remove First
<ul id="first" class="navigation">
<li ng-repeat="myname in mynames">{{myname.name}}</li>
</ul>
</div>
<div>
Lot of things in between
</div>
<ul id="second" class="popup" ng-controller="nameCtrl">
<li ng-repeat="myname in mynames">{{myname.name}}</li>
</ul>
JS
var testApp = angular.module('testApp', []);
testApp.service('nameService', function($http) {
var me = this;
me.mynames = [
{
"name": "Funny1"
},
{
"name": "Funny2"
},
{
"name": "Funny3"
},
{
"name": "Funny4"
}
];
//How to do
/*this.getNavTools = function(){
return $http.get('http://localhost/data/name.json').then(function(result) {
me.mynames = result.mynames;
return result.data;
});
};*/
this.addName = function() {
me.mynames.push({
"name": "New Name"
});
};
this.removeName = function() {
me.mynames.pop();
};
});
testApp.controller('nameCtrl', function ($scope, nameService) {
$scope.mynames = nameService.mynames;
$scope.$watch(
function(){ return nameService },
function(newVal) {
$scope.mynames = newVal.mynames;
}
)
$scope.addName = function() {
nameService.addName();
}
$scope.removeName = function() {
nameService.removeName();
}
});
jsfiddle
Next thing i want to do is to make a http request to json file and load my two section with data, and if i add or remove it should reflect in both areas.
Any pointers or exisisitng demo will be much helpful.
Thanks
The reason why only one ngRepeat is updating is because they are bound to two different arrays.
How could it happen? It's because that you have called getNavTools() twice, and in each call, you have replaced mynames with a new array! Eventually, the addName() and removeName() are working on the last assigned array of mynames, so you're seeing the problem.
I have the fix for you:
testApp.service('nameService', function($http) {
var me = this;
me.mynames = []; // me.mynames should not be replaced by new result
this.getNavTools = function(){
return $http.post('/echo/json/', { data: data }).then(function(result) {
var myname_json = JSON.parse(result.config.data.data.json);
angular.copy(myname_json, me.mynames); // update mynames, not replace it
return me.mynames;
});
};
this.addName = function() {
me.mynames.push({
"name": "New Name"
});
};
this.removeName = function() {
me.mynames.pop();
};
});
testApp.controller('nameCtrl', function ($scope, nameService) {
// $scope.mynames = nameService.mynames; // remove, not needed
nameService.getNavTools().then(function() {
$scope.mynames = nameService.mynames;
});
/* Remove, not needed
$scope.$watch(
function(){ return nameService },
function(newVal) {
$scope.mynames = newVal.mynames;
}
);
*/
$scope.addName = function() {
nameService.addName();
};
$scope.removeName = function() {
nameService.removeName();
};
});
http://jsfiddle.net/z6fEf/9/
What you can do is to put the data in a parent scope (maybe in $rootScope) it will trigger the both views ,And you don't need to $watch here..
$rootScope.mynames = nameService.mynames;
See the jsFiddle

Resources