React.js - Communicating between sibling components React.js - Communicating between sibling components javascript javascript

React.js - Communicating between sibling components


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.jsexport const changeDropdownValue = (index, value) => ({    type: 'CHANGE_DROPDOWN_VALUE',    index,    value});// helpers.jsexport 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.jsimport 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.


The following help 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>        );    }}