Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React.js - Communicating between sibling components

I'm new to React, and I'd like to ask a strategy question about how best to accomplish a task where data must be communicated between sibling components.

First, I'll describe the task:

Say I have multiple <select> components that are children of a single parent that passes down the select boxes dynamically, composed from an array. Each box has exactly the same available options in its initial state, but once a user selects a particular option in one box, it must be disabled as an option in all other boxes until it is released.

Here's an example of the same in (silly) code. (I'm using react-select as a shorthand for creating the select boxes.)

In this example, I need to disable (ie, set disabled: true) the options for "It's my favorite" and "It's my least favorite" when a user selects them in one select box (and release them if a user de-selects them).

var React = require('react');  var Select = require('react-select');        var AnForm = React.createClass({        render: function(){              // this.props.fruits is an array passed in that looks like:          // ['apples', 'bananas', 'cherries','watermelon','oranges']          var selects = this.props.fruits.map(function(fruit, i) {                var options = [                  { value: 'first', label: 'It\'s my favorite', disabled: false },                  { value: 'second', label: 'I\'m OK with it', disabled: false },                  { value: 'third', label: 'It\'s my least favorite', disabled: false }              ];                  return (                  <Child fruit={fruit} key={i} options={options} />              );          });              return (              <div id="myFormThingy">                  {fruitSelects}              </div>          )      }    });      var AnChild = React.createClass({        getInitialState: function() {          return {              value:'',              options: this.props.options          };      },        render: function(){            function changeValue(value){              this.setState({value:value});          }              return (              <label for={this.props.fruit}>{this.props.fruit}</label>              <Select                  name={this.props.fruit}                  value={this.state.value}                  options={this.state.options}                  onChange={changeValue.bind(this)}                  placeholder="Choose one"              />          )      }  });

Is updating the child options best accomplished by passing data back up to the parent through a callback? Should I use refs to access the child components in that callback? Does a redux reducer help?

I apologize for the general nature of the question, but I'm not finding a lot of direction on how to deal with these sibling-to-sibling component interactions in a unidirectional way.

Thanks for any help.

like image 533
JMcClure Avatar asked Mar 21 '16 23:03

JMcClure


People also ask

How do you pass data between two components siblings using React router?

Not surprisingly, to pass data between siblings, you have to use the parent as an intermediary. First pass the data from the child to the parent, as an argument into a callback from the parent. Set this incoming parameter as a state on the parent component, then pass it as a prop to the other child (see above example).

How do you pass data between two child components in React?

Just call an alert method in the childToParent function and pass that function as a prop to the child component. And in the child component, accept the childToParent function as a prop. Then assign it to an onClick event on a button. That's it!


1 Answers

TLDR: Yes, you should use a props-from-top-to-bottom and change-handlers-from-bottom-to-top approach. But this can get unwieldy in a larger application, so you can use design patterns like Flux or Redux to reduce your complexity.

Simple React approach

React components receive their "inputs" as props; and they communicate their "output" by calling functions that were passed to them as props. A canonical example:

<input value={value} onChange={changeHandler}> 

You pass the initial value in one prop; and a change handler in another prop.

Who can pass values and change handlers to a component? Only their parent. (Well, there is an exception: you can use the context to share information between components, but that's a more advanced concept, and will be leveraged in the next example.)

So, in any case, it's the parent component of your selects that should manage the input for your selects. Here is an example:

class Example extends React.Component {      constructor(props) {         super(props);         this.state = {             // keep track of what is selected in each select             selected: [ null, null, null ]          };     }      changeValue(index, value) {         // update selected option         this.setState({ selected: this.state.selected.map((v, i) => i === index ? value : v)})     }      getOptionList(index) {         // return a list of options, with anything selected in the other controls disabled         return this.props.options.map(({value, label}) => {             const selectedIndex = this.state.selected.indexOf(value);             const disabled = selectedIndex >= 0 && selectedIndex !== index;             return {value, label, disabled};         });     }      render() {         return (<div>             <Select value={this.state.selected[0]} options={this.getOptionList(0)} onChange={v => this.changeValue(0, v)} />             <Select value={this.state.selected[1]} options={this.getOptionList(1)} onChange={v => this.changeValue(1, v)} />             <Select value={this.state.selected[2]} options={this.getOptionList(2)} onChange={v => this.changeValue(2, v)} />         </div>)     }  } 

Redux

The main drawback of the above approach is that you have to pass a lot of information from the top to the bottom; as your application grows, this becomes difficult to manage. React-Redux leverages React's context feature to enable child components to access your Store directly, thus simplifying your architecture.

Example (just some key pieces of your redux application - see the react-redux documentation how to wire these together, e.g. createStore, Provider...):

// reducer.js  // Your Store is made of two reducers: // 'dropdowns' manages the current state of your three dropdown; // 'options' manages the list of available options.  const dropdowns = (state = [null, null, null], action = {}) => {     switch (action.type) {         case 'CHANGE_DROPDOWN_VALUE':             return state.map((v, i) => i === action.index ? action.value : v);         default:             return state;     } };  const options = (state = [], action = {}) => {     // reducer code for option list omitted for sake of simplicity };  // actionCreators.js  export const changeDropdownValue = (index, value) => ({     type: 'CHANGE_DROPDOWN_VALUE',     index,     value });  // helpers.js  export const selectOptionsForDropdown = (state, index) => {     return state.options.map(({value, label}) => {         const selectedIndex = state.dropdowns.indexOf(value);         const disabled = selectedIndex >= 0 && selectedIndex !== index;         return {value, label, disabled};     });     };  // components.js  import React from 'react'; import { connect } from 'react-redux'; import { changeDropdownValue } from './actionCreators'; import { selectOptionsForDropdown } from './helpers'; import { Select } from './myOtherComponents';  const mapStateToProps = (state, ownProps) => ({     value: state.dropdowns[ownProps.index],     options: selectOptionsForDropdown(state, ownProps.index) }};  const mapDispatchToProps = (dispatch, ownProps) => ({     onChange: value => dispatch(changeDropdownValue(ownProps.index, value)); });  const ConnectedSelect = connect(mapStateToProps, mapDispatchToProps)(Select);  export const Example = () => (     <div>         <ConnectedSelect index={0} />         <ConnectedSelect index={1} />         <ConnectedSelect index={2} />     </div> ); 

As you can see, the logic in the Redux example is the same as the vanilla React code. But it is not contained in the parent component, but in reducers and helper functions (selectors). An instead of top-down passing of props, React-Redux connects each individual component to the state, resulting in a simpler, more modular, easier-to-maintain code.

like image 149
iaretiga Avatar answered Sep 17 '22 13:09

iaretiga