gene b.
gene b.

Reputation: 11974

ReactJS Change Sibling State via Parent

My React structure is

- App
   |--SelectStudy
   |--ParticipantsTable

In SelectStudy there is a button whose click triggers a message to its sibling, ParticipantsTable, via the App parent. The first Child->Parent transfer works. But how do I implement the second Parent->Child transfer? See questions in comments.

App

class App extends Component { 

  myCallback(dataFromChild) {
      // This callback receives changes from SelectStudy Child Component's button click
      // THIS WORKS
      alert('SelectStudy Component sent value to Parent (App): ' + dataFromChild.label + " -> " + dataFromChild.value);
      // QUESTION: How to Update State of ParticipantsTable (SelectStudy's Sibling) next?
      // ........................................................
  }

  render() {
      return (
          <div className="App">
             <SelectStudy callbackFromParent={this.myCallback}></SelectStudy>
             <ParticipantsTable></ParticipantsTable>
          </div>
      );
}

SelectStudy

class SelectStudy extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            error: null,
            isLoaded: false,
            items: [],
            selectedStudy: null, 
            isButtonLoading: false
        };
        this.handleButtonClick = this.handleButtonClick.bind(this);
    }

    render() {
        const { error, isLoaded, items, itemsForReactSelect, selectedStudy, isButtonLoading } = this.state;
        return <Button onClick={this.handleButtonClick}>Search</Button>;
    }

    handleButtonClick = () => {
        this.props.callbackFromParent(this.state.selectedStudy);
    }

}

ParticipantsTable - this needs to receive a certain variable, e.g. study in its State

class ParticipantsTable extends React.Component {
    constructor(props) {
        //alert('Constructor');
        super(props);
        // Initial Definition of this component's state
        this.state = {
            study: null,
            items: [], 
            error: null
        };
    }

    // THIS METHOD IS AVAILABLE, BUT HOW TO CALL IT FROM App's myCallback(dataFromChild)?
    setStudy = (selectedStudy) => {
        this.setState({study: selectedStudy});
    }    

    render() {
        return ( <div>{this.state.study}</div> );
    }
}

Upvotes: 1

Views: 136

Answers (2)

Kevin Amiranoff
Kevin Amiranoff

Reputation: 14468

I think what you need to understand is the difference between state and props.

state is internal to a component while props are passed down from parents to children

Here is a in-depth answer

So you want to set a state in the parent that you can pass as props to children

1 set state in the parent

 this.state = {
  value: null
 }

 myCallback(dataFromChild) {
    this.setState({value: dataFromChild.value})

  }

2 pass it as a prop to the children

class ParticipantsTable extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            study: props.study,
            items: [], 
            error: null
        };
    }

Also, although not related to your question, if you learning React I suggest moving away from class-based components in favour of hooks and functional components as they have become more widely used and popular recently.

Upvotes: 1

jered
jered

Reputation: 11571

The state should live definitively at the App level, not in the child. State needs to live one level above the lowest common denominator that needs access to it. So if both SelectStudy and ParticipantsTable need access to the same bit of state data, then it must live in their closest common ancestor (or above).

This is a core concept of React, known as "lifting state up", so much so that it has its own page in the official React documentation.

In your case, it would look something like this. Notice how state lives in only one place, at the <App /> level, and is passed to children via props.

import React from 'react';

class App extends React.Component {
  // State lives here at the closest common ancestor of children that need it
  state = {
    error: null,
    isLoaded: false,
    items: [],
    selectedStudy: null,
    isButtonLoading: false
  };

  myCallback = (dataFromChild) => {
    this.setState(dataFromChild);
  };

  render() {
    return (
      <div className="App">
        {/* State is passed into child components here, as props */}
        <SelectStudy data={this.state} callbackFromParent={this.myCallback}></SelectStudy>
        <ParticipantsTable study={this.state.selectedStudy} />
      </div>
    );
  }
}

class SelectStudy extends React.Component {
  handleButtonClick = () => {
    // Here we execute a callback, provided by <App />, to update state one level up
    this.props.callbackFromParent({ ...this.props.selectedStudy, isButtonLoading: true });
  };

  render() {
    const { error, isLoaded, items, itemsForReactSelect, selectedStudy, isButtonLoading } = this.props.data;

    return <Button onClick={this.handleButtonClick}>Search</Button>;
  }
}

// This component doesn't need to track any internal state - it only renders what is given via props
class ParticipantsTable extends React.Component {
  render() {
    return <div>{this.props.study}</div>;
  }
}

Upvotes: 3

Related Questions