Parent displaying the title and other metadata of a child - reactjs

Let's say I have a main component which provides common elements for all views in my app:
var Main = React.createClass({
render: function() {
return (
<div id="main">
<MenuBar title={this.state.title} icon={this.state.title} />
<div id="view">{React.Children.only(this.props.children)}</div>
</div>
);
}
});
Then I could use it like this:
<Main>{router.routeComponent(path)}</Main>
Now, I would like the title property (and possibly other "meta" information about the child) of the menu bar be set by the child view in this case. My current idea for this is to have child views expose a .getMetadata() method and a onMetadataChanged={...} property and then have the child return an object like { title: "Foobar", icon: "images/foobar.png" } from the getMetadata() method.
A second option would be to repeat the Main component for every view like this:
var SomeView = React.createClass({
render: function() {
return (
<Main title="Some view" icon="images/foobar.png">
{/* view content here */}
</Main>
);
}
});
However, the disadvantage of this, if I understand correctly, is that the performance will be worse when switching view since React will not try to merge the DOM tree if the SomeView component changes to an entirely different component (i.e. SomeOtherView). This would mean that all of DOM nodes of Main would have to be recreated.
What would the idiomatic/best way be to do this in React?

The simplest solution would be an event emitter. You could also use flux if you're already using it.
var emitter = new (require('events').EventEmitter); // or from http://wzrd.in/standalone/events
var Main = React.createClass({
// apply the metadata to state when the event is emitted
componentDidMount: function(){
emitter.on('site-metadata', this.handleMetadata);
},
handleMetadata: function(meta){
this.setState({title: meta.title, icon: meta.icon});
},
// in willUnmount remove the listener
render: function() {
// ...
}
});
router.provideSiteMetadata = function(meta){
Object.assign(router.meta, meta);
emitter.emit('site-metadata', router.meta);
};
router.meta = {title: '', icon: null};
You then call router.provideSiteMetadata, and any components interested in this metadata will be notified, and can apply it to state.

Related

How to force React to accept server markup

On the first load I render my markup completely filled with data on the server.
This works well and the client sees it as a static html till the client-side React takes over. But this client-side code throws the markup away.
I stopped React from requesting the data per AJAX on the first load (because it's already there), only if I navigate client-side with a react-router <Link> to it.
But I can't force it to simply let the markup be, till the user interacts with it.
You can use dangerouslySetInnerHTML attribute. For example, create a new component wrapper like this and set html property:
export default React.createClass({
render: function() {
return <div dangerouslySetInnerHTML={ this.createMarkup() }>
</div>;
},
componentDidMount: function() {
// attach some events
},
componentWillUnmount: function() {
// detach some events
},
shouldComponentUpdate: function() {
return false;
},
createMarkup: function() {
return {
__html: this.props.html
};
}
});

how to access methods of an instance of a React component?

Is there a way to call the methods that a React component defines internally?
I understand generally we want to be passing values around declaratively with props/data etc. However I am using some component libraries that have internal useful methods.
eg
var field = <AutoComplete/>;
field.setValue("ready"); // doesn't work
eg this method
https://github.com/callemall/material-ui/blob/master/src/auto-complete.jsx#L244-L248
in material-ui AutoComplete component.
You can not do this with virtual dom, since virtual dom is just a description of components to be created(actual component instance will be created or updated only when rendering).
But you can access component instances inside your react component after rendering using refs:
var Test = React.createClass({
getInitialState: function(){
return {value:0};
},
setValue: function(value){
this.setState({value:value});
},
render: function() {
return <div>Value {this.state.value}</div>;
}
});
var App = React.createClass({
render: function() {
return <div>
<Test ref="test"/>
<button onClick={()=>this.refs.test.setValue(1)}>1</button>
<button onClick={()=>this.refs.test.setValue(2)}>2</button>
<button onClick={()=>this.refs.test.setValue(3)}>3</button>
</div>;
}
});
var mountNode = document.getElementById('app');
ReactDOM.render(<App name="John" />, mountNode);
jsbin with code above: http://jsbin.com/kitehujaje/1/edit?js,output

Add event handler to React.DOM element dynamically

I'm working with a RadioButtonGroup component which is like radio input but with buttons:
It would be good if using the component was easy like this:
var SelectThing = React.createClass({
render: function render() {
// I would not like to add onClick handler to every button element
// outside the RadioButtonGroup component
return (
<RadioButtonGroup onChange={this._onActiveChange}>
<button>Thing 1</button>
<button>Thing 2</button>
<button>Thing 3</button>
</RadioButtonGroup>
)
},
_onActiveChange: function _onActiveChange(index) {
console.log('You clicked button with index:', index);
}
});
The actual question: How can I achieve that the most elegantly with React? (I found another similar question but it doesn't exactly answer to this).
My first intuition was to add and remove the onClick handlers inside the component to remove boiler plate code from the component's user. Another option that comes to my mind is to give e.g. <p> elements instead of button elements and put them inside button elements which would be created inside the RadioButtonGroup component. I don't like the latter that much because it doesn't make that much sense semantically compared to passing buttons.
Here's what the (obviously not working) component looks like now:
// Radio input with buttons
// http://getbootstrap.com/javascript/#buttons-checkbox-radio
var RadioButtonGroup = React.createClass({
getInitialState: function getInitialState() {
return {
active: this.props.active || 0
};
},
componentWillMount: function componentWillMount() {
var buttons = this.props.children;
buttons[this.props.active].className += ' active';
var self = this;
buttons.forEach(function(button, index) {
// How to dynamically set onclick handler for virtual dom
// element created inside JSX?
button.addEventListener('onClick', function(event) {
self._onAnyButtonClick(index, event);
}
});
},
componentWillUnmount: function componentWillUnmount() {
var buttons = this.props.children;
buttons.forEach(function(button, index) {
button.removeEventListener('onClick');
});
},
render: function render() {
return (
<div className="radio-button-group">
{buttons}
</div>
)
},
_onAnyButtonClick: function _onAnyButtonClick(index, event) {
this.setState({
active: index
});
this.props.onChange(index);
}
});
You don't want to mess with click handlers on each button, just listen for the click on the container. Then update the state based on which child is clicked.
Also, with React it's best to keep all of your DOM stuff in the render function. In this case, defining an element's class name.
Here's how this could work:
var RadioButtonGroup = React.createClass({
getInitialState: function getInitialState() {
return {
active: this.props.active || 0
};
},
clickHandler: function clickHandler(e) {
// Getting an array of DOM elements
// Then finding which element was clicked
var nodes = Array.prototype.slice.call( e.currentTarget.children );
var index = nodes.indexOf( e.target );
this.setState({ active: index });
},
render: function render() {
var buttons = this.children.map(function(child, i) {
if (i === this.state.active) child.props.className += ' active';
return child;
}, this);
return (
<div className="radio-button-group" onClick={ this.clickHandler }>
{ buttons }
</div>
)
}
});
To get an api like this (similar to <input/>), we need to use the cloneWithProps addon.
<RadioButtonGroup onChange={this.handleChange} value={this.state.selected}>
<button>Test 1</button>
<button>Test 2</button>
<button>Test 3</button>
</RadioButtonGroup>
All this does is take each child, add a click handler, and conditionally add a className of 'active' to it. You easily can (and should) modify it to take the active class name as a prop.
var RadioButtonGroup = React.createClass({
render: function(){
return <div>{React.Children.map(this.props.children, this.renderItem)}</div>
},
renderItem: function(button, index){
return React.cloneElement(button, {
className: this.props.value === index ? ' active ' : '',
onClick: function(){
this.props.onChange(index);
}.bind(this),
key: index
});
}
});
demo
If you don't want to use cloneWithProps, you could use a wrapper div, but styling may be a bit more complex.
renderItem: function(button, index){
return React.createElement('div', {
className: this.props.value === index ? ' active ' : '',
onClick: function(){
this.props.onChange(index);
}.bind(this),
key: index
}, button);
}
The reason everything uses index is because you're passing react elements, which are opaque. There's no clean way to get any data out of these buttons, but we do know their index because we're iterating over them using React.Children.map. An alternative api would look like this:
<RadioButtonGroup value={'test1'} onChange={fn} options={{
test1: <button>Test 1</button>,
test2: <button>Test 2</button>,
test3: <button>Test 3</button>
}} />
Here we can iterate over this.props.options, and pass the key to the onChange callback, and take e.g. 'test1' as a value prop.
This is my code .I do not know whether this is the best way ,as i started using react just 5 hrs ago. But it works fine.
var LeftPane = React.createClass({
getInitialState: function() {
return {list:[{name:"Item 1",isactive:1},
{name:"Item 2",isactive:0},
{name:"Item 3",isactive:0},
{name:"Item 4",isactive:0},
{name:"Item 5",isactive:0}]};
},
clickHandler: function(index) {
var current_list = this.state.list;
current_list.map(function(record,i){
if(record.isactive==1){
if(i!=index){
record.isactive =0;
}else{
return;
}
}else{
if(i==index){
record.isactive =1;
}
}
});
this.setState({data:current_list});
},
render: function() {
var active_item = "list-group-item active"
var non_active_item ="list-group-item";
var _this = this;
return (
<div className="list-group">
{this.state.list.map(function(record,i) {
if(record.isactive==1){
return <a className={active_item} onClick={_this.clickHandler.bind(null, i)}>{record.name}</a>
}else{
return <a className={non_active_item} onClick={_this.clickHandler.bind(null, i)}>{record.name}</a>
}
})}
</div>
</div>
)
}
});
I would not like to add onClick handler to every button element
React uses events delegation pattern, so you can freely adding as many onClick handlers as needs.
Another way, if you just want to make code clear, you may create your own button component, and pass _onActiveChange to it props, but i don't sure that it's necessary in your case.
And remember, that React works with synthetic events, and, in some cases, usage of setState within native event handlers may cause an unpredictable behaviour.

Nested React <input> element loses focus on typing

I have:
A component App with a child component Filter.
The child needs to mutate state in the parent, which it is doing via an <input onChange={handler}>.
The handler is a prop that is set on the child by the parent.
All good so far.
However, whenever the a key is pressed on the input, it loses focus. I presume it's being destroyed and re-rendered.
If I hoist the Filter component up a level into the App and drive it off the state in that, then everything works as you'd expect, but obviously I'd like to be able to nest the components and share the state at the top level.
I guess calling setState at this higher level is causing the whole thing to get re-rendered, but I thought the diffing algorithm would be clever enough to avoid replacing the node in the Filter sub-component and thus avoid blurring the focus on the <input>.
What am I doing wrong / how can I fix this? Is there a better way to structure this?
Working JSBin here: http://jsbin.com/fexoyoqi/10/edit?html,js,output
var App = React.createClass({
getInitialState: function() {
return {
items: ["Tom", "Dick", "Harry"],
filterText: ''
};
},
setFilterText: function (event) {
this.setState({filterText: event.target.value});
},
render: function () {
var filter = React.createClass({
render: function () {
return <input value={this.props.filterText} onChange={this.props.onChange}/>;
}
});
var rows = this.state.items
.filter(function (item) {
return this.state.filterText == ''
? true
: item.toLowerCase().indexOf(
this.state.filterText.toLowerCase()) > -1;
}.bind(this))
.map(function(item) {
return <li>{item}</li>
});
return (
<div>
Filter: <filter filterText={this.state.filterText}
onChange={this.setFilterText}/>
<ul>
{rows}
</ul>
</div>
);
}
});
React.renderComponent(<App />, document.body);
You're creating a new component class inside the render function.
Part of react's diffing algorithm looks at the components, and if it sees you rendered a different type component in one spot it says "the structure is probably significantly different, so I won't waste time diffing the children". It throws out the node, and renders the new result to the DOM.
Move var filter = React.createClass... somewhere it's only executed once, and it'll work fine.

can we attach click handlers to custom child components

I was trying to add a click handler to my own child component. In react chrome extension I was able to see the click handler as well.
But the click itself didn't work - wondering what did I miss.
Sample Code:
...
render (
<MySampleComponent onClick={this.handler} />
);
...
MySampleComponent can take whichever props it wants; components don't automatically copy props to their children. If you want to be able to add an onClick handler to MySampleComponent, then you can support this in the definition of that component:
var MySampleComponent = React.createClass({
render: function() {
return <div onClick={this.props.onClick}>...</div>;
}
});
You can add the handler from the samecomponent or call it through props.
Below code looks for onClick param in props. If nothing is passed, then
it goes for default handler in the component(clickHandler).
var MySampleComponent = React.createClass({
clickHandler: function(){
// write your logic
},
render: function() {
return <div onClick={this.props.onClick || this.clickHandler}>...</div>;
}
});
and while using this in another component use it as below
...........
handler: function() {
// write your logic
},
render {
var self = this;
return (<MySampleComponent onClick={self.handler} />);
}
......

Resources