How to change the state of a sibling component in ReactJS for a Master/Slave checkbox system? - reactjs

I'm curious how to implement a Master/Slave checkbox system. The approach I am currently taking is to have an owner/ownee (parent/child) relationship between the Master/Slave checkboxes. However, I'm curious if there's a way to accomplish this in React if the checkboxes were siblings instead. I see here in the docs that it says to use your own global event system. Can someone please explain/show me an example of what they mean by this? Thanks!

In my Backbone+React application I use backbone events when I do not have any other options to communicate states between the components. I am pretty sure you can find other minimal event libraries or build your own to communicate events if you need to.
Below is an example code from the jsFiddle http://jsfiddle.net/kb3gN/2239/ ,I have supplied for your scenario.
From the 'MasterCheckbox' component's onChange event I trigger a global application wide 'checkbox:clicked' event on the event object that's accessible to other components/views.
//Global event object
window.App = _.extend({}, Backbone.Events);
//MasterCheckbox component's change handler
handleChange: function () {
this.setState({checked: this.refs.master.getDOMNode().checked});
App.trigger('checkbox:clicked', this.refs.master.getDOMNode().checked);
}
Then on the 'SlaveCheckbox' component I subscribe to that event and change the state of the
'SlaveCheckbox' component
componentDidMount: function(){
App.on('checkbox:clicked', function(state){this.setState({checked: state}) }.bind(this));
}

You could wrap the two checkboxes into their own component to make a MasterSlaveComponent. Wasn't sure of the exact functionality you would want, but here is an example where turning on Master also turns on Slave.
var MasterSlaveCheckbox = React.createClass({
getInitialState: function(){
return {
master: false,
slave: false
}
},
handleMasterChange: function() {
var newMasterState = !this.state.master;
if(newMasterState) {
this.setState({master: true, slave: true});
} else {
this.setState({master: false});
}
},
handleSlaveChange: function() {
this.setState({slave: !this.state.slave});
},
render: function() {
return <div>
<div>
<input type="checkbox" checked={this.state.master} onChange={this.handleMasterChange} />
Master
</div>
<div>
<input type="checkbox" checked={this.state.slave} onChange={this.handleSlaveChange} />
Slave
</div>
</div>;
}
});
https://jsfiddle.net/crob611/z6ozz62a/1/

Related

Why are component `refs` `undefined` when I try to access them?

I am new to React and I am using React v0.13.3 and JSXTransformer v0.13.3 to create a couple of simple components, each of which renders an input field along with a button inside a paragraph. When any button is clicked, I want to show the associated input value using an alert. I am trying to use refs to get the value, but for some reason it is not working, and shows undefined.
Here is my code:
var CommentBox = React.createClass({
show: function() {
alert(this.refs.test.value);
},
render: function() {
return(<p><input type="text" ref="test" /><button type="button" onClick={this.show}>Show</button></p>);
}
});
React.render(<div><CommentBox /><CommentBox /></div>, document.getElementById('commentbox'));
I would suggest to bind onChange of the input to set the value on the state, like so:
<input onChange={event => this.setState({value: event.target.value})} />
Now this.state.value always has the current value of the field. Then on the show function, just do:
show: function() {
alert(this.state.value);
}
Your code is working just fine! I put it in a jsfiddle.
However, that's not a good approach for your specific use-case. In general, you must try not to overuse refs. Here's a quote from the ReactJS related docs:
Your first inclination may be to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy.
So, here is a better approach:
For similar purposes, just like the one you need, using a controlled component is the preferred way. I suggest you to consider using your Component state.
Therefore, here's an example how you can achieve the same result, using the Component state. I am using your code snippet as a base:
var CommentBox = React.createClass({
getInitialState() {
return {
// That's the default input value
value: ''
};
},
show: function() {
alert(this.state.value);
},
handleChange: function(event) {
// Each time the input's value is changed, the state gets an update
this.setState({
value: event.target.value
});
},
render: function() {
return(
<p>
<input onChange={this.handleChange} type="text" />
<button type="button" onClick={this.show}>Show</button>
</p>
);
}
});
React.render(
<div><CommentBox /><CommentBox /></div>,
document.getElementById('commentbox')
);

React select onchange doesn't work

I am having problems with select tag implementation in React.js.
Here is what I have:
var ProfessorsFilter = React.createClass({
getInitialState: function(){
return(
{
visibleDepartments: [],
selectedSchoolId: 1
}
)
},
selectSchool: function(event){
console.log("select school");
alert("select ");
this.setState({selectedSchoolId: event.target.value});
},
render: function () {
var schoolOptions = this.props.schools.map(function(school, index){
return (
<option key={index} value={school.id}>{school.name}</option>
);
});
return(
<select onChange={this.selectSchool} value={this.state.selectedSchoolId} className="select-2">
{schoolOptions}
</select>
)
}
});
So, I am doing controlled component. But onchange does not fire my method selectSchool.
EDIT:
jsfiddle: https://jsfiddle.net/wc02bvaj/
BTW. I am also using select2.
EDIT2: It turned out to be problem with select2.
If you're using jQuery along with React on the same DOM elements it will generally cause problems. Either leave that unmanaged by React, use a React equivalent and remove jQuery, or in this case, you have a few options.
The easiest options is to just use https://github.com/rkit/react-select2-wrapper.
Alternatively you could roll your own solution, e.g.,
Select2 has an change callback on its own. If you're using Redux (it doesn't look like you are) you could change the state by dispatching an action from the Select2 change handler. You might be able to listen to the jQuery Select2 change event by registering a handler in your ProfessorsFilter and setting the state there.

react.js event listener on none react elements

I'm wondering, what's the best practice to add an event listener to a DOM element, which is NOT rendered by react (a radio button)
I have a given html form and I want to use the advantages of react.js instead of playing around with jQuery ajax stuff.
With jQuery I would do something like that:
$('input[type=radio][name=bedStatus]').change(function() {....}
Should I use something like this with react ()
componentDidMount: function() {
if (this.props.onWindowScroll) window.addEventListener("scroll", this.handleScroll);
},
componentWillUnmount: function() {
if (this.props.onWindowScroll) window.removeEventListener("scroll", this.handleScroll);
}
Could someone push me in the right direction?
Cheers Kai
For react elements created by your component:
render() {
return (
<div>
<input type="radio" onChange={this.handleChange} />
</div>
)
}
In terms of best practices, usually the non-react element should be a window or document. If it is a HTML element, then probably it should be wrapped in a react component and the event listener should be as above.
For non-react elements your approach is very good. It is very important to remove the listener when the component is unmounted.
Listeners are usually attached after the component is mounted:
componentDidMount: function() {
if (this.props.onWindowScroll) window.addEventListener("scroll", this.handleScroll);
}
Important: Remove when unmounted:
componentWillUnmount: function() {
if (this.props.onWindowScroll) window.removeEventListener("scroll", this.handleScroll);
}
I repeat that this is usually done to attach events on window or document objects.
Depending on your use case it can be acceptable to attach to HTML elements of parent or child elements or even of some elements created dynamically. findDOMNode() can be useful in some cases. But it is advised to avoid doing this whenever possible.

Angular 1.5, Calling a function in a component from Parent controller

Angular 1.5 components easily allow creating a call back to the parent from the component. Is there a way i can call a function in a component from a function in parent's controller ?
Lets say my component is called task-runner and below is the HTML for it in the parent container.
<task-runner taskcategogyid=5></task-runner>
<button type="button" ng-click="doSomethingInParent()">ParentToChildButton</button>
The plunkr is here. I want that when ParentToChildButton is clicked, the function doSomethingInParent() calls the remotefunc in component.
A few different ways:
Pass an object as an attribute with two-way binding (scope:{myattr:'='}) to the task-item-header directive which the directive could then add a function to for the parent controller to call.
Set an attribute that has either one-way binding (scope:{myattr:'#'}) on it and then attrs.$observe changes to it to trigger the action, or two-way binding (scope:{myattr:'='}) and then $scope.$watch changes to it to trigger the action.
Have the directive raise an event (scope:{raiseLoaded:'&onLoaded'}) that passes an object that represents a remote control object with a method on it that triggers the action you want. To raise the event, you'd call something like raiseLoaded({remoteControl: remoteControlObj}) within the directive, and then to listen to the event, you'd use <task-item-header on-loaded="setRemote(remoteControl)"> assuming you have a setRemote() method on your parent controller.
Update I just realized your question was for a newer version of AngularJS, so I'm not sure if my answer still applies. I'll leave it here for now, but if you find it is not helpful I can delete it.
I needed something like this previously so I thought I would share how I solved this problem.
Similar to the OP, I needed to freely trigger methods in child components from a parent component. I wanted to be able to trigger this method in the parent freely/separately without the use of the $onChanges lifecycle hook.
Instead I created a notification-registration mechanism to allow a child component to 'register' a method with the parent when it is loaded. This method can then be freely triggered by the parent outside of the $onChanges cycle.
I created a codepen to demonstrate this. It can be easily extended to handle different types of notifications from the parent that aren't related to the data changes.
Index.html
<div ng-app="tester">
<parent></parent>
</div>
Script.js
angular.module('tester', []);
angular.module('tester').component('parent', {
controller: parentController,
template: `
<div class="tester-style">
<button ng-click="$ctrl.notifyChild()">Notify child</button>
<child parent-to-child-notification-registration="$ctrl.childComponentNotificationRegistration(handler)">
</div>
`
});
function parentController() {
let childComponentEventHandler = null;
this.$onInit = function() {
this.value = 0;
};
this.childComponentNotificationRegistration = function(handler) {
childComponentEventHandler = handler;
console.log('Child component registered.');
};
this.notifyChild = function() {
if (childComponentEventHandler) {
childComponentEventHandler(this.value++);
}
};
}
angular.module('tester').component('child', {
bindings: {
parentToChildNotificationRegistration: '&',
},
controller: childController,
template: `
<div class="tester-style">
<h4>Child Component</h4>
</div>
`
});
function childController() {
this.$onInit = function() {
this.parentToChildNotificationRegistration({
handler: this.processParentNotification
});
};
this.processParentNotification= function(parentValue) {
console.log('Parent triggered child notification handler!!!');
console.log('Value passed to handler:', parentValue);
};
};
}
Also for something similar to #adam0101's #3 answer see codepen.

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.

Resources