2

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(); 
1
  • but you're not passing any props to <Preview /> Commented May 7, 2021 at 8:50

2 Answers 2

2

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)} /> ))} 
Sign up to request clarification or add additional context in comments.

Comments

0

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 } /> ); }; 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.