I use this simple React component only for example.
I would like to access this.setState() inside the functions 'working' and 'group.notWorking'.
var myComponent = React.createClass({
getInitialState: function() {
return {};
},
working: function() {
this.setState({ test: true }); //this is myComponent
},
group: {
notWorking: function() {
console.log(this); //this is window
}
},
render: function() {
return (
<div>
<ChildComponent working={this.working} group={this.group}/>
</div>
);
},
});
My question is how do you pass functions grouped in an object, or is there any best practice, to avoid passing all the functions one by one to children components.
You need to pass a bound version of it.
<ChildComponent working={this.working} group={this.group.notWorking.bind(this)}/>
If you want to pass the whole group you need to make it a function which returns an object and bind it:
group: function() {
return {
notWorking: function() {
console.log(this);
}.bind(this)
};
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
Related
So I've read that we should try avoiding refs when accessing Child components
(React refs with components)
(https://facebook.github.io/react/docs/more-about-refs.html)
In my case, however, I couldn't think of a way to avoid this in my situation..
Scenario:
I have a MyForm parent component which contains a TagInput child component, which resembles Stackoveflow's "Tags" input field. As the user types an input+SPACE, a tag is added to TagInput's internal state. When the user submits the form, the selected tag list is posted to my server.
Implementation:
var MyForm = React.createClass({
submit: function() {
var selectedTags = this.refs.tagInput.state.selectedTags;
$.post(
SERVER_URL,
data: { tags: selectedTags }
);
},
render: function() {
return (
<form onSubmit={this.submit}>
<TagInput ref="tagInput">
</form>
);
}
});
var TagInput = React.createClass({
getInitialState: function() {
return {
selectedTags: []
}
},
// This is called when the user types SPACE in input field
handleAddTag: function(tag) {
this.setState({
selectedTags: this.state.selectedTags.concat(tag);
});
},
render: function() {
return (
<form>
<ul>{this.state.selectedTags}</ul>
<input type="text" />
</form>
);
}
});
The above code works fine and does what is expected. The only concern is I'm using refs to directly access the Child component's internal state, and I'm not sure if this is the right "React" way.
I guess one option is to maintain the "selectedTags" state in MyForm instead of TagInput. This doesn't really make sense in my case, because in reality my form contains 5 TagInput components and many other states to manage..
Can anyone think of a way to improve my design? Or is using refs unavoidable in my case?
Thanks
You can pass handleAddTag from the parent component into the child and keep the state of the selected tags in the form component. Now when you submit the form you are pulling from the state on the form rather than using refs.
var MyForm = React.createClass({
getInitialState: function() {
return {
selectedTags: []
}
},
submit: function() {
var selectedTags = this.state.selectedTags;
$.post(
SERVER_URL,
data: { tags: selectedTags }
);
},
// This is called when the user types SPACE in input field
handleAddTag: function(tag) {
this.setState({
selectedTags: this.state.selectedTags.concat(tag);
});
},
render: function() {
return (
<form onSubmit={this.submit}>
<TagInput handleAddTag={this.handleAddTag} ref="tagInput">
</form>
);
}
});
//Use this.props.handleAddTag() to update state in Form Component
var TagInput = React.createClass({
render: function() {
return (
<form>
<ul>{this.state.selectedTags}</ul>
<input type="text" />
</form>
);
}
});
I found that using refs to get some piece of data is not as bad as using refs to modify the UI which is very bad as it will cause the UI to not be inline with your state or your application.
I want to pass a value to a component this way, but when I try to console log this.props.vouch it returns an undefined value.
I know it will work if I put:
<Something onClick={this.log} vouch=this.props.vouch />
and
ReactDOM.render(<List vouch="value 1"/>, document.getElementById('react-app'))
But I will want to use different vouch value later in the code and be able to reuse Something component.
var Something = React.createClass({
propTypes:{
vouch: React.PropTypes.string,
},
render: function() {
return (
<div>
<h1 onClick={this.props.onClick} vouch={this.props.vouch}>Click!</h1>
</div>
);
}
});
var List = React.createClass({
log: function() {
console.log(this.props.vouch);
},
render: function () {
return (
<Something onClick={this.log} vouch="value 1" />
<Something onClick={this.log} vouch="value 2" />
);
}
});
ReactDOM.render(<List />, document.getElementById('react-app'));
You can't set this.props from child component, but you can pass data using data attributes, like this
<h1 onClick={this.props.onClick} data-vouch={this.props.vouch}>Click!</h1>
...
log: function (e) {
console.log(e.target.dataset.vouch);
},
Example
or using .bind, like this
<h1 onClick={this.props.onClick.bind(null, this.props.vouch)}>Click!</h1>
...
log: function (vouch) {
console.log(vouch);
},
Example
or call callback in child component and pass props, like this
handleClick: function () {
this.props.onClick(this.props.vouch)
},
render: function() {
return (<div>
<h1 onClick={this.handleClick}>Click!</h1>
</div>)
}
...
log: function (vouch) {
console.log(vouch);
},
Example
You're not passing this.props.vouch to List, so your log will return undefined.
var Something = React.createClass({
propTypes:{
vouch: React.PropTypes.string,
},
onClick: function() {
this.props.onClick( this.props.vouch )
},
render: function() {
return (
<div>
<h1 onClick={this.onClick.bind( this )} vouch={this.props.vouch}>Click!</h1>
</div>
);
}
});
var List = React.createClass({
log: function( vouch ) {
console.log( vouch );
},
render: function () {
return this.props.vouch.map( vouch => <Something onClick={ log } vouch = { vouch } /> )
}
});
var vouch = [
{
value: 'foo'
},
{
value: 'bar'
}
]
ReactDOM.render(<List vouch={ vouch } />, document.getElementById('react-app'));
The actual problem of your log not working could also be solved by passing List.log to Something (which you do already) and then invoking it in the context of Something by using <h1 onClick={ this.props.onClick.call( this ) and having log console.log( this.props.vouch ) but this solution would be nasty from a maintainability standpoint.
It is important to understand the parent->child relationship between components that you are creating. At any point you can grab your vouch data and inject it but by injecting it at the List component you keep all children pure i.e. when you render you are passing the state of the system, you arent attempting to grab state or worse, mutate, state during the life-cycle of a render.
I'm trying to call a component function on a child component RouteHandler.
var Bar = React.createClass({
baz: function() {
console.log('something');
},
render: function() {
return <div />;
},
});
var Foo = React.createClass({
baz: function() {
this.refs['REF'].baz();
},
render: function() {
return <RouteHandler ref="REF" />;
},
);
Where RouteHandler is a Bar but this.refs['REF'].baz is undefined.
See https://facebook.github.io/react/tips/expose-component-functions.html for more information on component functions.
I don't believe react-router currently supports exposing component functions on RouteHandlers and the current hacky workaround is to do:
this.refs['REF'].refs['__routeHandler__'].baz();
See https://github.com/rackt/react-router/issues/1597
I have got the beginnings of a clickable list component that will serve to drive a select element. As you can see from the below, onClick of the ListItem, I'm passing the state of a child element (ListItem in this case) to the parents (SelectableList, and CustomSelect component). This is working fine. However, what I would also like to do is change the state of the sibling components (the other ListItems) so that I can toggle their selected states when one of the ListItems is clicked.
At the moment, I'm simply using document.querySelectorAll('ul.cs-select li) to grab the elements and change the class to selected when it doesn't match the index of the clicked ListItem. This works - to an extent. However, after a few clicks, the state of the component has not been updated by React (only by client side JS), and things start to break down. What I would like to do is change the this.state.isSelected of the sibling list items, and use this state to refresh the SelectableList component. Could anyone offer a better alternative to what I've written below?
var React = require('react');
var SelectBox = require('./select-box');
var ListItem = React.createClass({
getInitialState: function() {
return {
isSelected: false
};
},
toggleSelected: function () {
if (this.state.isSelected == true) {
this.setState({
isSelected: false
})
} else {
this.setState({
isSelected: true
})
}
},
handleClick: function(listItem) {
this.toggleSelected();
this.props.onListItemChange(listItem.props.value);
var unboundForEach = Array.prototype.forEach,
forEach = Function.prototype.call.bind(unboundForEach);
forEach(document.querySelectorAll('ul.cs-select li'), function (el) {
// below is trying to
// make sure that when a user clicks on a list
// item in the SelectableList, then all the *other*
// list items get class="selected" removed.
// this works for the first time that you move through the
// list clicking the other items, but then, on the second
// pass through, starts to fail, requiring *two clicks* before the
// list item is selected again.
// maybe there's a better more "reactive" method of doing this?
if (el.dataset.index != listItem.props.index && el.classList.contains('selected') ) {
el.classList.remove('selected');
}
});
},
render: function() {
return (
<li ref={"listSel"+this.props.key}
data-value={this.props.value}
data-index={this.props.index}
className={this.state.isSelected == true ? 'selected' : '' }
onClick={this.handleClick.bind(null, this)}>
{this.props.content}
</li>
);
}
});
var SelectableList = React.createClass({
render: function() {
var listItems = this.props.options.map(function(opt, index) {
return <ListItem key={index} index={index}
value={opt.value} content={opt.label}
onListItemChange={this.props.onListItemChange.bind(null, index)} />;
}, this);
return <ul className="cs-select">{ listItems }</ul>;
}
})
var CustomSelect = React.createClass({
getInitialState: function () {
return {
selectedOption: ''
}
},
handleListItemChange: function(listIndex, listItem) {
this.setState({
selectedOption: listItem.props.value
})
},
render: function () {
var options = [{value:"One", label: "One"},{value:"Two", label: "Two"},{value:"Three", label: "Three"}];
return (
<div className="group">
<div className="cs-select">
<SelectableList options={options}
onListItemChange={this.handleListItemChange} />
<SelectBox className="cs-select"
initialValue={this.state.selectedOption}
fieldName="custom-select" options={options}/>
</div>
</div>
)
}
})
module.exports = CustomSelect;
The parent component should pass a callback to the children, and each child would trigger that callback when its state changes. You could actually hold all of the state in the parent, using it as a single point of truth, and pass the "selected" value down to each child as a prop.
In that case, the child could look like this:
var Child = React.createClass({
onToggle: function() {
this.props.onToggle(this.props.id, !this.props.selected);
},
render: function() {
return <button onClick={this.onToggle}>Toggle {this.props.label} - {this.props.selected ? 'Selected!' : ''}!</button>;
}
});
It has no state, it just fires an onToggle callback when clicked. The parent would look like this:
var Parent = React.createClass({
getInitialState: function() {
return {
selections: []
};
},
onChildToggle: function(id, selected) {
var selections = this.state.selections;
selections[id] = selected;
this.setState({
selections: selections
});
},
buildChildren: function(dataItem) {
return <Child
id={dataItem.id}
label={dataItem.label}
selected={this.state.selections[dataItem.id]}
onToggle={this.onChildToggle} />
},
render: function() {
return <div>{this.props.data.map(this.buildChildren)}</div>
}
});
It holds an array of selections in state and when it handles the callback from a child, it uses setState to re-render the children by passing its state down in the selected prop to each child.
You can see a working example of this here:
https://jsfiddle.net/fth25erj/
Another strategy for sibling-sibling communication is to use observer pattern.
The Observer Pattern is a software design pattern in which an object can send messages to multiple other objects.
No sibling or parent-child relationship is required to use this strategy.
Within the context of React, this would mean some components subscribe to receive particular messages and other components publish messages to those subscribers.
Components would typically subscribe in the componentDidMount method and unsubscribe in the componentWillUnmount method.
Here are 4 libraries that implement the Observer Pattern. The differences between them are subtle - EventEmitter is the most popular.
PubSubJS: "a topic-based publish/subscribe library written in JavaScript."
EventEmitter: "Evented JavaScript for the browser." It's actually an implementation of a library that already exists as part of nodejs core, but for the browser.
MicroEvent.js: "event emitter microlibrary - 20lines - for node and browser"
mobx: "Simple, scalable state management."
Taken from: 8 no-Flux strategies for React component communication which also is a great read in general.
The following code helps me to setup communication between two siblings. The setup is done in their parent during render() and componentDidMount() calls.
class App extends React.Component<IAppProps, IAppState> {
private _navigationPanel: NavigationPanel;
private _mapPanel: MapPanel;
constructor() {
super();
this.state = {};
}
// `componentDidMount()` is called by ReactJS after `render()`
componentDidMount() {
// Pass _mapPanel to _navigationPanel
// It will allow _navigationPanel to call _mapPanel directly
this._navigationPanel.setMapPanel(this._mapPanel);
}
render() {
return (
<div id="appDiv" style={divStyle}>
// `ref=` helps to get reference to a child during rendering
<NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
<MapPanel ref={(child) => { this._mapPanel = child; }} />
</div>
);
}
}
The Facebook ReactJS library has strict rules about which component methods can be overridden and how. Unless it's specifically allowed, we cannot redefine a method.
For my custom mixins how can I update the SpecPolicy if I have a method I want to allow to be overridden? Is this even possible?
This example is a bit contrived but it should get the point across. Say I have the mixin below which is trying to provide a default renderItem method, intended to be overridden if necessary. When I attempt to render the component <Hello ... /> I get an Invariant Violation error. You can find a jsfiddle here.
var MyMixin = {
render: function () {
return this.renderItem();
},
renderItem: function () {
return <div>Hello {this.props.name}</div>;
}
};
var Hello = React.createClass({
mixins: [MyMixin],
renderItem: function() {
return <div>Hey {this.props.name}</div>;
}
});
This isn't possible right now. It's likely that a future version of React will have mixins that take advantage of ES6 classes and will be a bit more flexible. See here for a proposal:
https://github.com/reactjs/react-future/blob/master/01%20-%20Core/02%20-%20Mixins.js
You could just use something like jQuery extend to extend the object that's passed to React.createClass, instead of using a mixin - this would allow you to still use Mixins when you want, and use this method when you need to (JS Fiddle):
/** #jsx React.DOM */
var MyMixin = {
render: function () {
return this.renderItem();
},
renderItem: function () {
return <div>Hello {this.props.name}</div>;
}
};
var Hello = React.createClass($.extend(MyMixin, {
renderItem: function() {
return <div>Hey {this.props.name}</div>;
}
}));
React.renderComponent(<Hello name="World" />, document.body);
Maybe you can do something like this if you still want a default rednerItem implementation:
var MyMixin = {
render: function () {
return this.renderItem();
},
renderItem: function () {
var customRender = this.customRenderItem;
return customRender != undefined ? customRender() : <div>Hello {this.props.name}</div>;
}
};
var Hello = React.createClass({
mixins: [MyMixin],
customRenderItem: function() {
return <div>Hey {this.props.name}</div>;
}
});
The mixin has access to the component properties and state. You can have multiple implementations in the mixin and use the implement you need based on the properties/state.
Only thing you want to make sure that the components using the mixin have these properties/state or have a default implementation.
In situation, when i need to provide some properties to mixin, that depends on my react class, i make mixin as a function with needed arguments, that return mixin object
//mixin.js
module.exports = function mixin(name){
return {
renderItem(){
return <span>name</span>
}
};
}
//react-class.js
var myMixin = require('mixin');
module.exports = React.createClass({
mixins:[myMixin('test')],
render(){
return this.renderItem();
}
});
//result
<span>test</test>