Steve
Steve

Reputation: 122

React - can you pass state handler down multiple child component layers

I have a React parent component that holds state for the whole app. I want to pass a function down to a child component to so that when a button is clicked in that child then it changes the parent's state. Now I can do this for one layer, however when I've tried to pass the function down to a further layer (so a child component passing down a function it received as a prop) my app falls over with the following error:

TypeError: Cannot read property 'props' of undefined

Is it possible to pass this function down through multiple layers?

In my lowest level I don't have constructor set up as I thought that was only needed it I needed to initiate state in the child component, is that right?

I've detailed below the relevant parts of my code:

Parent:

  class App extends Component {
  constructor() {
    super();
    this.changeDisplay = this.changeDisplay.bind(this)
    this.handleAddTicket = this.handleAddTicket.bind(this)
    this.handleDeleteTicket = this.handleDeleteTicket.bind(this)

    this.state = {...}
...
}

 handleDeleteTicket(data){
    console.log(data)

  } ....

First Child

class Board extends React.Component {
return (
            <Container>
                <Row>
                    <Col sm={4}>
                        <Todo tasks={todoTasks} deleteTicket={this.props.deleteTicket}/>
                    </Col>
                </Row>
            </Container>
  )
}

Second Child:

class Todo extends React.Component {
render() {
todoTicketsAr = this.props.tasks.map(function (obj, i) {
                return <Ticket key={i} data={obj} deleteTicket={this.props.deleteTicket}></Ticket>
            })
        }

        return (
            <div>
                <h2>To do:</h2>
                {todoTicketsAr}
            </div>
                )
    }

So I'm binding this at the parent level but nowhere else, is that right?

Upvotes: 2

Views: 4589

Answers (2)

Win
Win

Reputation: 5584

Here's an example of how to do it with prop drilling and context API. I would use the Context API example because you don't need to keep prop drilling when you need to use different parts of the state within different components.

I have also provided a prop drilling example if you want to see that working and compare the two solutions.

React Context API Example

const Global = React.createContext({});

class Store extends React.Component {
  static Consumer = Global.Consumer;
  state = {
    value: 'bacon',
  };
  changeValue = (value) => this.setState({ value });
  render() {
    const { value } = this.state;
    const { changeValue } = this;
    return (
      <Global.Provider value={{
        value,
        changeValue,
      }}>
        {this.props.children}
      </Global.Provider>
    )
  }
}

class Child extends React.Component {
  state = {
    text: '',
  };
  handleChange = (evt) => this.setState({ text: evt.target.value });
  render() {
    const { text } = this.state;
    const { handleChange } = this;
    return (
      <Store.Consumer>
        {({ value, changeValue }) => (
          <div>
            <h3>Value is {value}</h3>
            <h5>Type a new value and submit</h5>
            <input value={text} onChange={handleChange} />
            <button onClick={() => changeValue(text)}>Submit</button>
          </div>
        )}
      </Store.Consumer>
    )
  }
}

const Layout = () => (
  <div>
    <h5>An example component that is not exposed to context</h5>
    <Child/>
  </div>
)

const Main = () => (
  <Store>
    <h2>React Context API example</h2>
    <Layout/>
  </Store>
);

ReactDOM.render(<Main/>, document.body);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

Prop drilling example

const ChildOfChild = ({ toggleDisplay }) => (
  <div>
    <button onClick={toggleDisplay}>Toggle in Child of Child</button>
  </div>
);

const Child = ({ toggleDisplay }) => (
  <div>
    <button onClick={toggleDisplay}>Toggle in Child</button>
    <ChildOfChild toggleDisplay={toggleDisplay} />
  </div>
);

class Main extends React.Component {
  state = {
    display: true,
  };
  toggleDisplay = () => {
    this.setState((prevState) => ({
      display: !prevState.display,
    }));
  };
  render() {
    const { toggleDisplay } = this;
    return (
      <div>
        <h2>React Prop drilling Example</h2>
        <pre>{this.state.display.toString()}</pre>
        <button onClick={this.toggleDisplay}>Parent</button>
        <Child toggleDisplay={toggleDisplay} />
      </div>
    );
  }
}

ReactDOM.render(
  <Main/>,
  document.body
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Upvotes: 3

jmcruzmanalo
jmcruzmanalo

Reputation: 39

The only time that would happen is because this is no longer bound correctly. One common situation I could give is:

The will throw an error

class App extends Component {
     someClickHandler() {
         /** 
          * This line will throw an error onClick 
          * of the button because `this` is not bound to the right thing.
          * If you log out `this` you'll probably see the click event object. 
          */
         console.log(this.state); 
     }
     render() {
          return (
              <button onClick={this.someHandler}>Random button</button
          )
     }
}

To properly bind this you have to do either

<button onClick={() => this.someHandler()}>Random button</button>

// or

<button onClick={this.someHandler.bind(this)}>Random button</button>

Upvotes: 0

Related Questions