refs vs onChange - reactjs

I've recently started learning react.js (loving it!) and ran into an interesting scenario with handling input values.
In the egghead tutorial they had you update an input using a ref like so:
handleSubmit(){
var newNote = this.refs.note.value;
this.refs.note.value = '';
this.props.addNote(newNote);
}
<input type="text" ref="note" />
<button type="button" onClick={this.handleSubmit.bind(this)}>
Submit
</button>
Later, I was playing with the material ui library (also awesome) and ran into a problem where I couldn't update one of the material components (Probably because of this issue) using a ref. After some time on Google I found that you could use a the state, and just update it using an onChange function, like so:
handleNoteChange(e){
this.setState({newNote: e.target.value});
}
<TextField
type="text"
value={this.state.newNote}
onChange={this.handleNoteChange.bind(this)} />
It seems like using a ref would be easier, however, in my recent time learning react native, everything you do seems to be the second way, using an onChange function and a state variable.
So my question is, moving forward, would it be better to use one over the other? Maybe there are limitations which make using the state better on native?

In React, avoid manipulating the DOM using references. You should never do anything like this:
this.refs.note.value = '';
It's fine to read from the DOM when necessary (usually in response to user input), but the only way information should pass from your application to the DOM is via your render() method. Otherwise, the DOM becomes out of sync with your application. In React, your "source of truth" lives in memory, not in the DOM.
The second example you provide is the standard React way. The view - as defined by the render() method - is ultimately derived from props and state. Changing the state triggers a change in view. So calling this.setState() will force your component to re-render.

I ran into the same situation while doing a Pluralsight tutorial on react. I thought I would share my experience and add another example as well. The tutorial altered the value of in input via it's ref value.
ReactDOM.findDOMNode(...)
Using ref shortens the code, but also alters the input value directly instead or relying on component state.
var Form = React.createClass({
onSubmit: function(e){
e.preventDefault();
var loginInput = ReactDOM.findDOMNode(this.refs.login);
this.props.addCard(loginInput.value);
loginInput.value = '';
},
render: function(){
return (<form onSubmit={this.onSubmit}>
<input placeholder="login" ref="login"/>
<button>Add</button>
</form>);
}
});
I modified the code to access and modify the input value via state.
var Form = React.createClass({
getInitialState: function(){
return {inputLogin: ''}
},
onKeyUpHandler: function(e){
this.setState({inputLogin: e.target.value})
},
onSubmit: function(e){
e.preventDefault();
this.props.addCard(this.state.inputLogin);
this.setState({inputLogin: ''})
},
render: function(){
return (<form onSubmit={this.onSubmit}>
<input placeholder="login" onKeyUp={this.onKeyUpHandler}/>
<button>Add</button>
</form>);
}
});
It's a little more verbose but now its using state properly.

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 on attribute change

React component change when state change but can it change when attribute value changes.
var Hello = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
var name="foo";
React.render(<Hello name={name} />, document.body);
name="bar";
Above I would like to see 'Hello bar' rendered.
I'm not aware of any JavaScript platform that would take a local variable and automatically trigger an update of a UI component as you've shown (there's no practical and efficient way to monitor the variable for changes without using a timer).
While some JavaScript frameworks you may have encountered do change tracking on the properties of objects, ReactJS currently is not one of those.
In all React cases, you'll trigger the change by re-creating and rendering the component. But, as this is really just a virtual-DOM, it's not necessarily causing the DOM to be changed significantly (making it potentially more efficient).
In the simple case below, it would just mean that you call render again with the new value of "bar".
var Hello = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
var name="foo";
React.render(<Hello name={name} />, document.body);
name = "bar";
React.render(<Hello name={name} />, document.body);
In a more complex scenario, you might instead have a container component that is wired to changes from a Store and triggers the UI to be updated (again, by following a pattern like above, or you might find yourself needing to use forceUpdate from within a container/component). You might choose to follow the Flux pattern (more information).
React is designed with the assumption that component props may be treated as immutable (they don't change) once a component is rendered, and this will be enforced in future versions of React (as of React v0.13 this will trigger warnings), see here: React V0.13 Changelog.
If you want to change the value of the property after render, you'll need to re-render the component with the new prop values.
In this case you will have to call forceUpdate on the React component.
which works to refresh.
c = React.render(<Hello name={name} />, document.body);
c.forceUpdate();
However it's a sign of some kind of bad design. Ideally use setState to keep data in sync. Or pass the property from the parent component and call setState on the parent component.

react.js component: can't get keyDown events if component inside another one

JS Bin Available here
I have a component called ComponentB for which I want to get key down events:
var ComponentB = React.createClass({
render: function() {
return (
<div
contentEditable
onKeyDown={this.handleKeyDown}>{this.props.data}</div>
);
},
handleKeyDown: function(e) {
console.log("B: " + e.type +"-" + e.which);
}
});
I can get those events if this ComponentB is directly under the main/App component.
If I try to embed this component inside another component (componentA) I don't receive those events anymore (this.props.lines is an array of 3 strings) :
var ComponentA = React.createClass({
render: function(){
return (
<div
contentEditable>
{
this.props.lines.map(function(line,i) {
return <ComponentB key={i} data={line} />
})
}
</div>
);
}
});
The associated JS BIN shows this behavior : if you try to edit the lines in the component A section. no event will be emitted but they will if you edit the sinbgle instance of componentB below...
Looks to me like a bug in react.js but wanted to check here first.
As #ssorallen said in a comment, you can't have nested contentEditable elements, with or without react.
It seems to be a problem with nested contentEditable elements. Remove contentEditable from ComponentA, and it works as you expect.
One of the reasons this doesn't work, is because React doesn't really support contentEditable. Basically it sets the attribute, but it can't sensibly render into the contentEditable because when it tries to update it... the DOM has changed without its knowledge (which throws an error).
Rather you should treat contentEditable elements like an input element, which doesn't have children. Instead you update the innerHTML (see renderComponentToString and dangerouslySetInnerHTML) and listen to onInput events to get changes. See Stack Overflow: onChange event for contentEditable for details on how this works.
A proper handling of contentEditable has been discussed briefly, but no solution was arrived at. Feel free to suggest good ways to handle it.

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

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/

Resources