React on attribute change - reactjs

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.

Related

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.

refs vs onChange

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.

ReactJS component rendering and how to append elements to the div the component is mounted on

I'm having this use case where there is a 'load more' button on the page to fetch more items from an API endpoint, and so I want these items to get appended to the DOM when they arrive. The thing is that the HTML page that is served by the web server comes with some extra list items (as seen below) within the same item-list div container I mount my React component, with empty data/props, on page load.
<div class="item-list">
<div class="item-list__child-item"></div>
<div class="item-list__child-item"></div>
...
<div class="item-list__child-item"></div>
</div>
My assumption is that if I handle this in the ReactJS way, as soon as I fetch more items from the server (REST) and append those items to an 'itemList' state array, react will somehow replace all of the content that holds that 'item-list' div where the component was mounted on.
A quick workaround that I'm thinking would work and that it doesn't rely on the isomorphic stuff and pre-rendering the react component on the server, is to create a separate sibling div having the same div class name 'item-list' and adding an id attribute to mount the component on, so the resulting HTML would go like:
<div class="item-list">
<div class="item-list__child-item"></div>
<div class="item-list__child-item"></div>
...
<div class="item-list__child-item"></div>
</div>
<div class="item-list" id="react-component-name"></div>
Maybe there is a cleaner way to do it without getting into the isomorphic stuff, or maybe I'm not understanding the React concept and how it works. Anyways will appreciate any directions you may have on this.
OK, your question wasn't clear on this, but the data that is represented by what was generated in the HTML will be entirely different from the data that you will be getting via AJAX.
There's a simple solution to this. Instead of creating an entirely new DOM element that will be adjacent to your original DOM layout, what you will do is grab the data that was already there, store it into an array, and append the new data that you will grab via AJAX into that Array. This way, you will reap the benefit of React's DOM diffing. Why is it useful? Maybe you want to let the user sort the data, or interact with the data directly, while it will remain in full control of a parent React component.
So anyways, take a look at this fiddle: https://jsfiddle.net/x4jjry04/3/. It's based on Paul Booblic's fiddle.
var Page = React.createClass({
getDefaultProps: function () {
return {
items: []
}
},
getInitialState : function(){
return{
items : this.props.items
}
},
componentDidMount: function () {
// Mimics an AJAX call, but replace this with an actial AJAX call.
setTimeout(function () {
var dataFromAjax = ['one', 'two', 'three'];
this.setState({
items: this.state.items.concat(dataFromAjax)
});
}.bind(this));
},
addClick : function(){
this.state.items.push("more");
this.forceUpdate();
},
render : function(){
return <div>{this.state.items.map(function(item){return <div className="bla-bla-class">{item}</div>})}<br/><div onClick={this.addClick}>ADD</div></div>;
}
});
var elements = document.querySelectorAll('.item-list__child-item');
var initialvalues = Array.prototype.slice
.call(elements)
.map(function (div) {
return div.innerHTML;
});
React.render(<Page items={initialvalues} />, document.body);
Check this simple fiddle demo:
https://jsfiddle.net/x4jjry04
var Page = React.createClass({
getInitialState : function(){
return{
items : ["one","two","three"]
}
},
addClick : function(){
this.state.items.push("more");
this.forceUpdate();
},
render : function(){
return <div>{this.state.items.map(function(item){return <div className="bla-bla-class">{item}</div>})}<br/><div onClick={this.addClick}>ADD</div></div>;
}
});
React.render(<Page />, document.body);
I assume you are using react serverside to render the list?
On page load you fetch the original list from the server and have the component "re-render" the elements. re-render is in quotes, because React wont actually update the list unless the list changes. Now you are setup with a component that works as expected, and you can add elements to the list as you want.
The general Idea with isomorphic/universal React is that you treat your app as a normal Single Page App, and let React handle the magic of dirty checking.
This also means that you can use the same component when rendering on the server, since your component doesn't contain any client specific code.

Is there another way to render in ReactJs

I am new to ReactJs. From the examples, I can see that one needs to call
React.render(elementToBeReadered, targetingElement). Is there a way to use the web components defined in React directly, like angularjs' directive? E.g.
<Hello />
var Hello = React.createClass({
render: function() {
return (
<div>
Hello World!
</div>
);
}
});
So that I don't need to add a target element like <div id='target-element'></div> and then render it with React.render(<Hello />, document.getElementById('target-element')). Why should I duplicate this everywhere?
You'll typically nest react components within each other. In angular, this would be similar to having many ng-app on many different elements.
If you want to have regular DOM, with react components only sparsely populated, then you'll have to render by element reference as you said. I would try to use react components to compose the entire app instead.
Is there a way to use the web components defined in React directly, like angularjs' directive?
Sure, you can build any system you like on top of React.render. You give it a react element and a dom node, and it does its thing. You could build an angular directive that renders the component you like, for example:
var reactComponents = {Foo: Foo};
module.directive('react', function($parse){
return {
link: function(scope, element, attrs){
var props = Object.keys(attrs).reduce(function(props, key){
if (key === "component") return props;
props[key] = $parse(attrs[key])(scope);
return props;
}, {});
var reactElement = React.createElement(reactComponents[attrs.component], props);
React.render(reactElement, element);
}
};
});
not tested
And in your template:
<react component="Foo" bar="1" baz="something.otherThing"></react>
If you add some watchers it'll respond to expressions changing, and you can do other things like error handling, resolving the component class with $injector rather than a static object hash, and handling '$destroy' (see React.unmountComponentAtNode).

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.

Resources