pass variable to function via bind in react component - reactjs

Below is the code snippet from a navigation menu example, which is from this blogpost. What I don't understand is the use of self variable and bind method.
I think this refers to MenuExample when it is stored as self, but I don't know how this changed inside <li>?
Also why is that onClick={this.clicked(index)} doesn't work? In this context what does this refers to?
var MenuExample = React.createClass({
getInitialState: function(){
return { focused: 0 };
},
clicked: function(index){
// The click handler will update the state with
// the index of the focused menu entry
this.setState({focused: index});
},
render: function() {
// Here we will read the items property, which was passed
// as an attribute when the component was created
var self = this;
// The map method will loop over the array of menu entries,
// and will return a new array with <li> elements.
return (
<div>
<ul>{ this.props.items.map(function(m, index){
var style = '';
if(this.state.focused == index){
style = 'focused';
}
// Notice the use of the bind() method. It makes the
// index available to the clicked function:
return <li className={style} onClick={self.clicked.bind(self, index)}>{m}</li>;
}) }
</ul>
<p>Selected: {this.props.items[this.state.focused]}</p>
</div>
);
}
});
// Render the menu component on the page, and pass an array with menu options
ReactDOM.render(
<MenuExample items={ ['Home', 'Services', 'About', 'Contact us'] } />,
document.getElementById('container')
);

onClick={this.clicked(index)} doesn't work because onClick expects a function, not a result of it. Normally, in a click handler, an event will be passed to it, but here they are using bind() to override that behavior and pass the component and the index as 'this' and the first argument.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Related

How to delete an input field from list of input properly

I am trying to handle a list of input dynamically. However when I put some content inside my inputs and delete one from my list I have a undesired behaviour. The correct item is removed but the item in the last position takes the state of the previous item.
var ListItem = React.createClass({
getInitialState: function() {
return {
content: ''
}
},
handleChange: function(e) {
this.setState({
content: e.target.value
})
},
render: function() {
return <div>
<input type="text" onChange={this.handleChange} />
</div>
}
});
var App = React.createClass({
getInitialState : function(){
return {
items : []
}
},
deleteElement: function(index, e) {
this.setState({
items: this.state.items.filter(function (e, i) {
return i !== index;
})
});
},
addElement: function() {
this.setState({
items: this.state.items.concat(<ListItem />)
});
},
render: function() {
var list = this.state.items.map(function(item, i) {
return <li key={ i }>
<p onClick={ this.deleteElement.bind(this, i) }>(-)</p>
<span>{ item }</span>
</li>
}, this);
return <div>
<ul>{ list }</ul>
<p onClick={ this.addElement }>(+)</p>
</div>
}
});
ReactDOM.render(
<App />,
document.getElementById('container')
);
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
I think that the issue could be about my list keys but I do not understand why. How could I remove my list elements without altering any states ?
Thanks
When iterating in render, React reuses component instances (for obvious performance reasons). To discriminate between instances, the prop key is used.
In your case when you remove an element, indexes move and therefore you mess with the key -> instance association. So list items after the deleted element are not associated with the right instance anymore, that is why some of your list elements seem to have the state of the previous element.
To avoid this problem you should never use index as key prop. Use a unique identifier for the data instance instead.
In your case, you could generate a unique identifier when adding a new element, and use it as key.
For more information about how lists work in React, read: https://facebook.github.io/react/docs/lists-and-keys.html

AngularJS component binding a function instead of emitting an event

I am building a simple view:
<tabulation tabulation-data="vm.tabs"></tabulation>
<div ng-switch="vm.activeTab.id">
<account-details ng-switch-when="details"></account-details>
<account-history ng-switch-when="history"></account-history>
<account-summary ng-switch-when="summary"></account-summary>
<account-dashboard ng-switch-when="dashboard"></account-dashboard>
</div>
Essentially, as I have it working now, tabulation will $emit an event to the parent account controller, which will update the vm.activeTab property to toggle through the different tab content.
A colleague of mine told me it may be more elegant to use bindings (&) on the tabulation component, which will use a function passed by the parent account component...
Unfortunately, I don't seam to understand how it functions:
Parent account controller:
function PocDemoContainerController($scope) {
var vm = this;
vm.tabs = [{
label: 'Details',
id: 'details'
},
{
label: 'History',
id: 'history'
},
{
label: 'Summary',
id: 'summary'
},
{
label: 'Dashboard',
id: 'dashboard'
}];
vm.activeTab = vm.tabs[0];
// this is the function that I want to pass to the tabulate component
vm.onClickTab = function (tab) {
vm.activeTab = tab;
};
...
}
Tabulate component html:
<tabulation tabulation-data="vm.tabs" on-click-tab="vm.onClickTab(tab)">
<div class="tabulation">
<nav class="container">
<button class="tabulation__mobile-toggle"
ng-class="{'tabulation__mobile-toggle--is-open': vm.mobileTabulationIsOpen}"
ng-click="vm.toggleMobileTabulation()">{{vm.activeTab.label}}</button>
<ul class="tabulation__container"
ng-class="{'tabulation__container--is-open': vm.mobileTabulationIsOpen}">
<li class="tabulation__item"
ng-repeat="tab in vm.tabs"
ng-class="{'tabulation--is-active': vm.isTabActive(tab)}">
<a id={{tab.id}}
class="tabulation__link"
ng-click="vm.onClick(tab)">{{tab.label}}</a>
</li>
</ul>
</nav>
</div>
</tabulation>
Tabulate controller:
...
module.exports = {
template: require('./tabulation.html'),
controller: TabulationController,
controllerAs: 'vm',
bindings: {
tabulationData: '<',
onClickTab: '&' // this should send data up, right?
}
};
Tabulation controller:
function TabulationController($scope) {
var vm = this;
...
vm.onClick = function (tab) {
vm.onClickTab(tab); // This is the function from the parent I want to call
};
...
}
TabulationController.$inject = [
'$scope'
];
module.exports = TabulationController;
So, the tabulation controller can see and call vm.onClickTab but the parameter value that is being passed is not passed to the parent account component controller...
How do I achieve this? (is it even possible that way?)
Alright! I finally found out how to do it, and it was not at all intuitive (the angular docs don't tell you this).
Based on the example above, my tabulation needs to tell the parent component which tab is now active so that it can update the view.
Here is how:
Declare a function on your parent component:
vm.onClickTab = function (tab) { ... };
Put the function on your child component.
<tabulation on-click-tab="vm.onClickTab(tab)"
IMPORTANT: If you are passing an argument, use the same name as the one you will define in your child component.
Inside your child component, declare a new function, and it is that function that should call the parent's callback.
vm.onClick = function (tab) { ... };
Now, here comes the part that is not mentioned anywhere: You have to call the parent's callback function using an object, with a property that uses the same name defined when passing that callback to the child component:
.
function TabulationController($scope) {
...
vm.onClick = function (tab) {
// The onClickTab() function requires an object with a property
// of "tab" as it was defined above.
vm.onClickTab({tab: tab});
};
...
}
Now, when vm.onClick() is called, it calls the parent's callback with an object, passing it the argument, and the parent can now use that data to update the view.
Looking at your Tabulate component html I wonder what is the tab parameter you are sending. Is it a local property of your controller? it seems not because there is no vm prefix to it (or any other name you've defined). Your code seems legit, it is the parameter origin that is not clear, therefore undefined.
Give us a hint on its origin for further analysis.
I had a similar problem and found this solution. I'm not sure if it's the best one but it works.
In the parent controller
I call my component
<contact button-action="vm.select(targetContact)"/>
And define my function
function select(contact) {...}
In my contact component
I define the binding:
bindings: { buttonAction: '&' }
And call the function
<button type="button" ng-click="$ctrl.buttonAction()">Click me</button>
When I click on my component button, select function is called passing the targetContact

Sending data to an indirect child in React.js on a click event

I have an application that has two major components (Landing and Skills):
App = React.createClass({
render() {
return (
<div>
<Landing />
<Skills category={category}/>
</div>
);
}
});
Within "Landing", I have a SocialMenu component, that has a list of items (the list of items is fed to SocialMenu like: <SocialMenu items={ ['Home', 'Services', 'About', 'Contact us']} />. On click, the item that is clicked is highlighted.
SocialMenu = React.createClass({
getInitialState: function(){
return { focused: 0 };
},
clicked: function(index){
this.setState({focused: index});
},
var self = this;
return (
<div>
<ul className="testblocks">{ this.props.items.map(function(m, index){
var style = '';
if(self.state.focused == index){
style = 'focused';
}
return <li key={index} className={style} onClick={self.clicked.bind(self, index)}>{m}</li>;
}) }
</ul>
<p>Selected: {this.props.items[this.state.focused]}</p>
</div>
);
}
});
What I would like to have happen, is have the index data from SocialMenu, passed to the Skills component.
However, I am not sure how to do that, because SocialMenu is a child of Landing, and not of Skills. (pretty much I would like to keep the list in landing, but have the output of the click of that list be put in Skills).
How would I do this?
You need something like Flux to have communication betweeen components that don't have parent-child relationship:
For communication between two components that don't have a
parent-child relationship, you can set up your own global event
system. Subscribe to events in componentDidMount(), unsubscribe in
componentWillUnmount(), and call setState() when you receive an event.
Flux pattern is one of the possible ways to arrange this.
So, for your case, you need to have a event like SELECT_SOCIAL that will pass the index information and your Landing component will have a listener to receive the index information.
You really don't need Flux for this. You probably will at some point, but if this is all you need to do, why don't you move the state up to the App component, and send it Landing & Skills components as props. You would probably also need to pass the clicked handler as a prop to SocialMenu.

Passing a function down to a component through props - REACT

I'm trying to pass the 'handleItemClick' function from the 'Dropdown' component, down to they 'Team' component, however, I can't get it past the 'List' component. The strange thing is, is that when I log the props inside the 'List' it says that 'itemClick' is in the 'this.props' object and yet when I set it as a prop on the 'Team' component it says "Cannot read property 'itemClick' of undefined".
Any help would be much appreciated.
Dropdown Component:
var Dropdown = React.createClass({
getInitialState: function(){
return {
display: false
};
},
handleClick: function(e){
this.setState({display: !this.state.display})
},
handleItemClick: function(e){
console.log("Something");
},
render: function(){
return (
<div className="dropdown">
<Button whenClicked={this.handleClick} className="btn-default" title={this.props.data.title} number={this.props.data.teams.length} />
<List teams={this.props.data.teams} display={this.state.display} itemClick={this.handleItemClick}/>
</div>
);
}
});
List Component:
var List = React.createClass({
render: function(){
console.log(this.props)
var teams = this.props.teams.map(function(team){
return <Team key={team} team={team} teamChoice={this.props.itemClick} />
});
var displayStyle = {
display: this.props.display ? 'block' : 'none'
};
return (
<ul style={displayStyle} className="dropdown-menu">
{teams}
</ul>
);
}
});
Kristofen44 was close:
Array.prototype.map() loses the this from your parent scope inside it's callback. But the function includes a variable input for accessing this within it:
var teams = this.props.teams.map(function(team){
return <Team key={team} team={team} teamChoice={this.props.itemClick} />
}, this);
I'm not sure but I think the error resides in List Component's render function when you map team to generate nodes. the map callback function loses the context 'this'. I think you have to bind the callback function to 'this' explicitly.
Like so :
var teams = this.props.teams.map(function(team){
return <Team key={team} team={team} teamChoice={this.props.itemClick} />
}.bind(this)); // <------
BTW, being quite new to react, I don't know if it's good practice to pass an whole object to the attribute 'key' of your Team component... I wonder if it's not better to just pass an id or something like that..
Hope it helps

ReactJS: Control a child state from the child and the parent

I have a rather simple problem and I'm not sure how to solve it with React's one way data flow.
Say you have a link in the parent that shows a modal
In the modal, you have an "X" that closes it.
I know I can change the state of the modal from the parent via props
// In the parent
<Modal display={this.state.showModal} />
// In the modal
<div className={this.props.display ? "show" : "hide"}>
<a className="close">×</a>
...
</div>
And I know how to close the modal, but not both. Not sure how to keep a state that is shared and controllable by both the parent and the child modal.
UPDATE
In trying to keep this as modular as possible, I think the React way would be to store the open/close logic in the modal variable.
var ParentThing = React.createClass({
...
render (
<Modal /> // How can I call this.open in the modal from here?
)
});
var Modal = React.createClass({
setInitialState: function() {
return {
display: false
}
},
close: function() {
this.setState({ display: false });
},
open: function() {
this.setState({ display: true });
},
render: function() {
return (
<div className={this.state.display ? "show" : "hide"}>
<a className="close" onClick={this.close}>×</a>
</div>
)
}
});
I saw this method, but it seems to be a little more than I need to do here. Reactjs: how to modify child state or props from parent?
There are two ways to handle this kind of thing in React:
Make the child "controlled," just like a form input with a value and onChange property, where the owner of the input controls the input.
Make the child "uncontrolled," just like a form input without a value.
The second choice seems faster up front, but just like managing a collection of form inputs in React, the advantage to using fully controlled components becomes apparent as complexity builds and the need to fully describe your UI at any point and time increases. (See this excellent answer from FakeRainBrigand if you're curious exactly why controlled components is better than uncontrolled in most cases.)
However, just like form inputs, there's no reason your component can't be either controlled or uncontrolled. If the user passes a display and onClose property, like Austin Greco's answer, you have a controlled modal, and the parent fully decides when to show or hide the modal.
If the user doesn't, you can skip using the properties and instead delegate to internal state managed by public methods on the modal component:
var ParentThing = React.createClass({
...
render: function() {
return <Modal ref="modal" />;
},
handleSomeClick: function() {
this.refs.modal.open();
}
});
var Modal = React.createClass({
setInitialState: function() {
return {
display: false
}
},
close: function() {
this.setState({ display: false });
},
open: function() {
this.setState({ display: true });
},
render: function() {
return (
<div className={this.state.display ? "show" : "hide"}>
<a className="close" onClick={this.close}>×</a>
</div>
)
}
});
If you like the idea of a controlled Modal component, but don't want to do all the boilerplate typing, you could even go so far as to implement something like the valueLink property for the Modal to simplify this pattern.
var ParentThing = React.createClass({
...
mixins: [React.addons.LinkedStateMixin],
getInitialState: function() {
return { showModal: false };
},
render: function() {
return <Modal displayLink={this.linkState("showModal")} />;
},
handleSomeClick: function() {
this.setState({showModal: true});
}
});
var Modal = React.createClass({
close: function() {
this.props.displayLink.requestChange(false);
},
render: function() {
return (
<div className={this.props.displayLink.value? "show" : "hide"}>
<a className="close" onClick={this.close}>×</a>
</div>
)
}
});
(See my blog post on creating custom components that work with linkState/valueLink for more info.)
So now you get the benefit of using a fully parent-controlled Modal, but you've removed a portion of the boilerplate around creating a function that sets the value to false and passing it to the modal.
You could pass a callback as a prop to the child component:
// In the parent
<Modal display={this.state.showModal} onClose={this.closeModal} />
// In the modal
<div className={this.props.display ? "show" : "hide"}>
<a className="close" onClick={this.props.onClose}>×</a>
...
</div>
Then when you click the close button on the child, it will call the function of the parent

Categories

Resources