简体   繁体   中英

How do you pass an onClick function to grandchild component in React?

I'm just starting out with React, adapting the tic tac toe tutorial for my case. I'm trying to click on the grandchild component to change the state of the grandparent component. Code is as follows:

class App extends React.Component {
  
  constructor(props) {
    super(props);
    this.state = {
      fields: [
        {
          id: 1,
          show: false
        },
        {
          id: 2,
          show: false
        }
      ]
    }
  }

  handleClick(i) {
    const fields = this.state.fields.slice();
    fields[i].show = true;
    this.setState({fields: fields});
  }
  
  render() {return <Preview />}
      
}

const Preview = (props) => {
  
  return (
    <div className="preview">
      {props.fields.map((field) => (
        <Field data={field} key={field.id} onClick={ props.onClick(field.id) }/>
      ))}
    </div>
  );
};

const Field = props => {
  
  return (
    <div className="field" onClick={ props.onClick } />
  );
};

I get a TypeError: Cannot read property 'state' of undefined from this line:

handleClick(i) {
const fields = this.state.fields.slice();

Issues

  1. this of the App class isn't bound to the handleClick function. This is cause of TypeError: Cannot read property 'state' of undefined error.
  2. You are mutating your state object. Slicing the array creates a new array reference, but fields[i].show = true; mutates the object reference in state.
  3. You don't pass fields or onClick props to Preview .
  4. The onClick callback isn't called correctly in Preview .

Solution

  1. Bind this to the handler or convert to arrow function so it is automatically bound.

     constructor(props){... this.handleClick = this.handleClick.bind(this); }

    or

    handleClick = (i) => {..... };
  2. DON'T MUTATE STATE. Shallow copy state then update properties.

     handleClick = (id) => { this.setState(prevState => ({ fields: prevState.fields.map((field) => { return field.id === id? {...field, show: true, }: field; }), })); };
  3. Pass fields and handleClick as onClick to Preview .

     render() { return ( <Preview fields={this.state.fields} onClick={this.handleClick} /> ); }
  4. Call props.onClick correctly with the id.

     {props.fields.map((field) => ( <Field data={field} key={field.id} onClick={() => props.onClick(field.id)} /> ))}

I've added some explanations, check the comments

  // [...]

  render() {
    // Here you need to pass "fields" and "handleClick" as props:
    return <Preview fields={this.state.fields} onClickField={this.handleClick} />
  }
      
}

const Preview = (props) => {
  // Here you get the props:
  const { fields, onClickField } = props;

  // Your onclick was a function call instead of just a function
  return (
    <div className="preview">
      {fields.map((field) => (
        <Field 
          data={field}
          key={field.id}
          onClick={() => onClickField(field.id) }
        />
      ))}
    </div>
  );
};

const Field = props => {
  
  return (
    <div className="field" onClick={ props.onClick } />
  );
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM