'React - calling function from a sibling component

Let's say I have a component tree as follows

<App>
  </Header>
  <Content>
      <SelectableGroup>
          ...items
      </SelectableGroup>
  </Content>
  <Footer />
</App>

Where SelectableGroup is able to select/unselect items it contains by mouse. I'm storing the current selection (an array of selected items) in a redux store so all components within my App can read it.

The Content component has set a ref to the SelectableGroup which enables me to clear the selection programatically (calling clearSelection()). Something like this:

class Content extends React.Component {

    constructor(props) {
        super(props);
        this.selectableGroupRef = React.createRef();
    }

    clearSelection() {
        this.selectableGroupRef.current.clearSelection();
    }

    render() {
        return (
            <SelectableGroup ref={this.selectableGroupRef}>
                {items}
            </SelectableGroup>
        );
    }

    ...

}

function mapStateToProps(state) {
    ...
}

function mapDispatchToProps(dispatch) {
    ...
}

export default connect(mapStateToProps, mapDispatchToProps)(Content);

I can easily imagine to pass this clearSelection() down to Contents children. But how, and that is my question, can I call clearSelection() from the sibling component Footer?

  • Should I dispatch an action from Footer and set some kind of "request to call clear selection" state to the Redux store? React to this in the componentDidUpdate() callback in Content and then immediately dispatch another action to reset this "request to call clear selection" state?
  • Or is there any preferred way to call functions of siblings?


Solution 1:[1]

I think the context API would come handy in this situation. I started using it a lot for cases where using the global state/redux didn't seem right or when you are passing props down through multiple levels in your component tree.

Working sample:

import React, { Component } from 'react'
export const Context = React.createContext()

//***************************//

class Main extends Component {

  callback(fn) {
    fn()
  }

  render() {
    return (
      <div>
        <Context.Provider value={{ callback: this.callback }}>
          <Content/>
          <Footer/>
        </Context.Provider>
      </div>
    );
  }
}

export default Main

//***************************//

class Content extends Component {

  render() {

    return (
      <Context.Consumer>
        {(value) => (
          <div onClick={() => value.callback(() => console.log('Triggered from content'))}>Content: Click Me</div>
        )}
      </Context.Consumer>
    )
  }
}

//***************************//

class Footer extends Component {

  render() {

    return (
      <Context.Consumer>
        {(value) => (
          <div onClick={() => value.callback(() => console.log('Triggered from footer'))}>Footer: Click Me</div>
        )}      
      </Context.Consumer>
    )
  }
}

//***************************//

Assuming content and footer and in there own files (content.js/footer.js) remember to import Context from main.js

Solution 2:[2]

According to the answer of Liam , in function component version:

export default function App() {
  const a_comp = useRef(null);

  return (
    <div>
      <B_called_by_a ref={a_comp} />
      <A_callB
        callB={() => {
          a_comp.current.f();
        }}
      />
    </div>
  );
}

const B_called_by_a = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    f() {
      alert("cleared!");
    }
  }));
  return <h1>B. my borther, a, call me</h1>;
});



function A_callB(props) {
  return (
    <div> A I call to my brother B in the button
      <button onClick={() => { console.log(props); props.callB();}}>Clear </button>
    </div>
  );
}

you can check it in codesandbox

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 varoons
Solution 2