简体   繁体   中英

React Hooks “Cannot read property 'setState' of undefined”

I'm making a to-do list. I want my list items to become line-through when I mark the checkbox. Therefore I am trying to make a const called markComplete. I make use of component drilling by sending my props from component 'TodoItem' to 'Todos' to 'App'.

The problem: In markComplete I try to access this because I want to change the state of the list item I checked. Though because I'm using Hooks in this project + the fact that I am kinda new, I struggle to find a solution to the error "Cannot read property 'setState' of undefined". Link to my error

So I believe with Hooks, setState() is not really a thing anymore. I think have to make use of useEffect() , but I'm not so sure. It might have something to do with binding, but I do bind in 'TodoItem' and I do use arrow functions.

Could anyone have a quick look at my code and tell me what I am missing here? Thanks in regard.

App.js

import React, { useState } from 'react';
import Todos from './components/Todos';
import './App.css';

const App = (props) => {
  const [todos, setTodos] = useState(
    [
      {
        id: 1,
        title: 'Learn React',
        completed: true
      },
      {
        id: 2,
        title: 'Eat lunch',
        completed: false
      },
      {
        id: 3,
        title: 'Meet up with friends',
        completed: false
      },
    ]
  );

  const markComplete = (id) => {
    console.log(id);

    this.setState([{todos: this.todos.map(todo => {
      if(todo.id === id) {
        todo.completed =! todo.completed
      }
      return todo;
    })}]);
  }

  return (
    <div className="App">
      {/* Todos.js is being called. Also props are being passed in Todos.js. */}
      <Todos todos={todos} markComplete={markComplete}/>
    </div>
  );


}

export default App;

  // // Log all todos in objects
  // console.log((todos));

  // // Log the 1st todo object
  // console.log((todos[0]));

  // // Log the title of the first todo
  // console.log((todos[0].title));

Todos.js

import React from 'react';
import TodoItem from './TodoItem';
import PropTypes from 'prop-types';

function Todos(props) {
  // console.log(props.todos)

  return (
    <div>
        <h1>My to do list</h1>

        {props.todos.map(todo => { // using props in child component and looping

            return (
                // Outputting all the titles from todo. It is more clean to make a seperate component for this.
                // <li>{todo.title}</li> 

                // Calling a seperate component called ToDoItem in order to return the titles.
                <TodoItem key={todo.id} todo={todo} markComplete={props.markComplete} />
            )
        })}

    </div> 
  );
}

// Defining proptypes for this class. In app.js, we see that Todos component has a prop called 'todos'. That needs to be defined here.
Todos.propTypes = {
  todos: PropTypes.array.isRequired
}

export default Todos;

TodoItem.js

import React, { useCallback } from 'react'
import PropTypes from 'prop-types';

function TodoItem(props) {
    // Change style based on state. 
    // By using useCallback, we can ensure the function App() is only redefined when one of its dependencies changes.
    // In this, case that is the dependencie 'completed'.
    // If you use id / title in the getStyle as well, you have to define these in the useCallback.
    const getStyle = useCallback(() => {  
        return {
            backgroundColor: '#f4f4f4',
            padding: '10px',
            borderBottom: '1px solid #ccc',

            // If-else text decoration based on state
            textDecoration: props.todo.completed ? 
            'line-through' : 'none'
        }
    }, [props.todo.completed]);

    // Destructuring
    const { id, title } = props.todo

    return (
        // Call out a function to change style based on state
        <div style={getStyle()}>
            <p> 
                { /* Start of ladder: passing the state though to Todos.js */ }
                { /* We use bind to see which checkbox is being marked. We use id to make this distinction.  */ }
                <input type="checkbox" onChange={props.markComplete.bind(this, id)}
                /> { ' ' }
                { title }
                {props.todo.title}
            </p>
        </div>
    )
}

// Defining proptypes for this class. In Todos.js, we see that TodoItem component has a prop called 'todo'. That needs to be defined here.
TodoItem.propTypes = {
    todo: PropTypes.object.isRequired
}

// const itemStyle = {
//     backgroundColor: '#f4f4f4'
// }

export default TodoItem

You are trying to use this.setState in a functional component which is App. You instead need to use setTodos which is the setter for your todos state. Also use the callback approach to update state since your current state is dependent on previous state

const markComplete = (id) => {
    console.log(id);

    setTodos(prevTodos => prevTodos.map(todo => {
      if(todo.id === id) {
        todo.completed =! todo.completed
      }
      return todo;
    }));
}

Read more about useState hook in the documentation here

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