How do I set state of sibling components easily in React? - reactjs

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>
);
}
}

Related

Access parent context when using this.props.children in React

Given the following, is it possible to access the parent context rather than the containers from a child (non-react component) element?
The example logs container, ideally it would log parent. I would like for Parent to be self contained, not to have it's state managed by its container.
var Container = React.createClass({
getInitialState: function () {
return {
context: 'container'
}
},
render: function () {
return (
<Parent>
<a href="#" onClick={function () {console.log(this.state.context);}.bind(this)}>click me</a>
</Parent>
);
}
});
var Parent= React.createClass({
getInitialState: function () {
return {
context: 'parent'
}
},
render: function () {
return (
<div>
{this.props.children}
</div>
);
}
});
If there is another pattern for handling this, please share as well.
Note: To be clear, I understand how the this keyword works and why the above example works as it does. The example is simply meant to illustrate the problem.
You can import some React helpers for that:
var React = require('react')
...
var children = React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
context: this.state.context
})
})
render() {
return <div>{ children }</div>
}
...
Then your child component will have this.props.context which will be the string 'parent', but this must be a React component, as this needs to refer to the component using the parent prop
var YourComponent = React.createClass({
render() {
return (
<a href="#" onClick={() => console.log(this.props.context)}>
click me
</a>
)
}
})
------
var Parent = require('./Parent')
var YourComponent = require('./YourComponent')
...
render() {
return <Parent><YourComponent /></Parent>
}
I do not know about the first part of your question, but since you commented about dynamically creating components, here's how I do it:
You can set a state variable in the constructor of the class and its parent:
if (typeof this.state == 'undefined') {
this.state = {
componentsToRender: <div></div>
};
}
Then in the parent component, in the componentDidMount() function:
var componentsToRender = [];
if ([conditional]) {
// some logic so you know which component to render
componentsToRender.push(<customChildComponentToRender key={} />);
}
else {
componentsToRender.push(<otherComponentToRender key={} />);
}
this.setState({
componentsToRender: <div>{componentsToRender}</div>
});
Make sure to put a key (lines 4 and 7 of the second code block) or React will scream at you.
In response to your initial question, I would watch this video from the ReactJS Conference 2015 to get more of the heart behind a container. After hearing what the guys at Facebook say (who have radical views on containers!), you might want to rethink the design to make your container more of a data layer.
I would check out THIS article from the react website. I think it might give you some intuition on solving your problem.
As a general rule of thumb, I try and only use this.state to handle internal UI state of a specific component. Everything else is passed via props. If you're needing the full context of a component, I would either pass it as a prop or checkout something like flux or redux which will help you manage state between components.

Should react component delegate onChange events to parent?

I'am using react with Flux architecture. And I have a question about best practices.
We have two component: container and item:
var Contaner = React.createClass({
getInitialState: function () {
return {
items: [ 'hello', 'world' ]
}
},
onClick: function () {
console.log('clicked!');
},
render: function () {
return
<div>
{this.state.items.map(function (item) {
return <Item data={item} onClick={this.onClick} />
})}
</div>;
}
});
var Item = React.createClass({
getInitialState: function () {
render: function () {
return
<div>
<button onClick={this.props.onClick}>{this.props.data}</button>
</div>
}
});
So, the question is: should I delegate onClick to parent component (container) or implement it in item component and why?
Depends on what you need as this in the click handler. Do you need to access container state or member variables or methods in the click handler? If yes, then by all means define the handler as method of container and pass it to the item as a property. If not, then the logical place for the handler is the item itself.
If you need to access both, for example to alter states of both components, then use both: implement two handlers and have the item onClick handler call the one passed from the parent as a property.

How to share states between different component while none of them are connected to each other by any parent child relation?

I am new to React and I find it interesting. I have a button and clicking on that button will set some state inside that class as true and based on that the title of the button will change.
I want to change some other React component in some other class based on this state change. How can I do that ?
For example :
var Comp1 = React.createClass({
getInitialState: function() {
return { logged_in: false }
},
handleClick: function() {
this.setState({logged_in: !this.state.logged_in});
},
render: function() {
return <button onClick={this.handleClick}>
{this.state.logged_in ? "SIGN OUT" : "SIGN IN"}
</button>;
}
})
Here is my second component which is rendered separately.
var Comp2 = React.createClass({
render: function(){
return <div>
{Comp1.state.logged_in ? "You are in" : "You need a login"}
</div>;
}
})
This is one component. I want to use the state of this component in another component either by passing the stat (though I am not using both the component in composite format, so they are independent by nature) or based on change in the state I can define another sate in the second component.
You need to provide a shared object that can handle the state that these two objects represent. It is going to be easier to pick a standard model for doing this, like redux or flux, than to come up with your own. Learning one of these patterns is crucial to development in React. I personally recommend redux.
This is a very stripped down version of what Flux would look like. It is not intended to be a real solution, just to provide a glimpse of the pattern. If you just want to skip to the code, here is a codepen.
The shared object is typically referred to as a store. Stores hold state, and provide methods to subscribe to changes in that state. Mutating this state would usually by done by calling publishing an event with dispatcher that notifies the store, but for simplicity I have included a publish function on the store itself
let loginStore = {
isLoggedIn: false,
_subscribers: new Set(),
subscribeToLogin(callback) {
this._subscribers.add(callback);
},
unsubscribeToLogin(callback) {
this._subscribers.delete(callback);
},
publishLogin(newState) {
this.isLoggedIn = newState;
this._subscribers.forEach(s => s(this.isLoggedIn));
}
};
Once a pub/sub system is in place, the components will need to subcribe to it. The login button mutates this state, so it will also publish.
class LoginButton extends React.Component {
constructor(...args) {
super(...args);
this.state = {isLoggedIn: loginStore.isLoggedIn};
}
update = (isLoggedIn) => {
this.setState({isLoggedIn});
}
componentDidMount() {
loginStore.subscribeToLogin(this.update);
}
componentDidUnmount(){
loginStore.unsubscribeToLogin(this.update);
}
handleClick = () => {
loginStore.publishLogin(!this.state.isLoggedIn);
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isLoggedIn ? "SIGN OUT" : "SIGN IN"}
</button>
);
}
}
class LoginHeader extends React.Component {
constructor(...args) {
super(...args);
this.state = {isLoggedIn: loginStore.isLoggedIn};
}
update = (isLoggedIn) => {
this.setState({isLoggedIn});
}
componentDidMount() {
loginStore.subscribeToLogin(this.update);
}
componentDidUnmount(){
loginStore.unsubscribeToLogin(this.update);
}
render() {
return (
<div>{this.state.isLoggedIn ? "You are in" : "You need a login"}</div>
);
}
}
You'll notice the second component does not refer to the state of the first component, but the state of the store. As you mentioned, since they do not have a reference to each other, and do not have a common parent, they cannot depend directly on each other for the loggedIn state.

React: Child communication in TodoList example

I'm learning React and modifying one of the tutorials. It's a Todo list, and I've come to a roadblock. I am trying to add the ability to delete items of the list. So I added an "x" to the end of each TodoList. But how can the child modify the parent's state.items? What is the "React" way of solving this?
var TodoList = React.createClass({
render: function() {
var createItem = function(itemText, index) {
return <li key={index + itemText}>{itemText} <a onClick={function() { // modify the parent item variable somehow?; }}>x</a></li>
};
return <ul>{this.props.items.map(createItem)}</ul>;
}
});
var TodoApp = React.createClass({
getInitialState: function() {
return {items: [], text: ''};
},
onChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = '';
this.setState({items: nextItems, text: nextText});
},
render: function() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
);
}
});
React.render(<TodoApp />, mountNode);
If you want to modify a parent's state from its child, you will need to create a function in your parent component that modifies the state in the way you want, then pass it to the child as a prop. Then, the child can just call this.props.functionName(args), and it will fire the function you created on the parent.
To follow your list example, let's say we have a List component that looks like this:
var List = React.createClass({
getInitialState : function () {
return ({items : []});
},
removeItem : function (num) {
var items = this.state.items;
items.splice(num, 1);
this.setState({items : items});
},
addItem : function () {
var value = React.findDOMNode(this.refs.itemName).value;
var items = this.state.items;
items.push(value);
this.setState({items : items});
},
render: function() {
var items = this.state.items.map(function(item, i) {
return <Item name={item} key={i} num={i} remove={this.removeItem} />
}.bind(this));
return (
<div>
List:<br/>
{items}
<br/>
<input type='text' ref='itemName'/>
<button onClick={this.addItem}>Add Item</button>
</div>
);
}
});
...and an Item component that looks like this:
var Item = React.createClass({
render : function () {
return (
<div>
{this.props.name}
<button onClick={this.props.remove.bind(null, this.props.num)}>Remove</button>
</div>
);
}
});
All this List does is hold on to the Items and provide an interface for which users can add and remove Items. Adding an Item is easy, we just need a button that, when clicked, takes the value of an <input> and adds it to our List's state. React will see the state transition and re-render the List for us, which will display the new Item.
Unlike adding an Item to the end of a list, the remove function needs to be a bit more specific regarding which Item needs to be removed. For that reason, we need a button on each Item that, when clicked, will remove the item from the list by calling this.props.remove and binding its num prop to the arguments. This will cause the removeItem function on List to fire, which will remove that item from our state. Again, React will see the state transition, and re-render our List for us, with the removed Item omitted.
Here is what that code looks like in action: https://jsfiddle.net/rxnpr9sq/
Hope that helps! Let me know if you have any additional questions.
I think this is what you need :https://facebook.github.io/react/tips/expose-component-functions.html
Michael Parker took the time to write your specific implementation this way.

Sending events between components

Consider the ToDo App example on the React homepage. For posterity, here's a fiddle and the code is at the end of this post.
Now say we decide to upgrade this app with w simple features:
Each todo item will have not only text, but also a "done" attribute. You can click on an item and it will toggle the "done" state, and perhaps add strikethrough styling when it is done.
At the very bottom, there will be text indicating the number of "Done", eg, "2 items done, 3 left to do"
The problem is that the state of the items is maintained in TodoApp, not in TodoList. So we'd like to add an onClick={something} to the <li> element in TodoList's render method. But we want that click event to be handled by TodoApp, which would then change the state of the item, and cause everything to re-render. If we wanted to approach it like this, how could we do it?
We could also create a TodoItem component to be called by TodoList, and push the statefulness down into that. This would allow the click to be handled by the TodoItem, but now we would need a way to share the TodoItems' states with the component indicating the number of items done and still todo.
In a nutshell, I'd like to know how components can send events to each other, because I think just knowing that would allow solutions to both problems.
React ToDo App
/** #jsx React.DOM */
var TodoList = React.createClass({
render: function() {
var createItem = function(itemText) {
return <li>{itemText}</li>;
};
return <ul>{this.props.items.map(createItem)}</ul>;
}
});
var TodoApp = React.createClass({
getInitialState: function() {
return {items: [], text: ''};
},
onChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = '';
this.setState({items: nextItems, text: nextText});
},
render: function() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
);
}
});
React.renderComponent(<TodoApp />, document.body);
The idiomatic way to do this is to pass a callback down to TodoList:
Live demo: http://jsbin.com/zeqizene/1/edit
I've changed TodoList to look like this:
var TodoList = React.createClass({
handleDoneToggle: function(i) {
this.props.onDoneToggle(i);
},
render: function() {
var createItem = function(item, i) {
return <li onClick={this.handleDoneToggle.bind(null, i)}>
{item.text}
{item.done && " (done)"}
</li>;
};
return <ul>{this.props.items.map(createItem, this)}</ul>;
}
});
When an item is clicked, TodoList will call its own onDoneToggle function, so TodoApp can modify the state appropriately.
See also Editing a rich data structure in React.js.

Resources