Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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; 
like image 271
The Pied Pipes Avatar asked Jun 15 '15 13:06

The Pied Pipes


People also ask

How will you set the state properties in React components?

Changing the state Object To change a value in the state object, use the this. setState() method. When a value in the state object changes, the component will re-render, meaning that the output will change according to the new value(s).

How do I change the state of component in React?

To update our state, we use this. setState() and pass in an object. This object will get merged with the current state. When the state has been updated, our component re-renders automatically.

How do you pass data between sibling components using React router?

you can't pass data through siblings, you need to lift up state or use other strategies. you need to find a common ancestor (in your case App. js) to place your state, and pass down your state and your setState function as props to Shop and Pdp . this is the simplest approach.


2 Answers

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/

like image 76
Colin Ramsay Avatar answered Sep 20 '22 12:09

Colin Ramsay


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.

like image 35
Mrchief Avatar answered Sep 21 '22 12:09

Mrchief