为什么我的 useState 钩子在这个 React 组件中不起作用?

Why isn't my useState hook working in this React component?

我整天都被困在这个问题上,希望能得到一些帮助。提前致谢。

起初我是这样写的,但我会得到一个类型错误:todos.map不是一个函数

function toggleState() {
  setTodos(state => ({ ...state, isComplete: !state.isComplete }))
}

最后我意识到错误是因为它正在将待办事项作为对象返回,所以我尝试了这个:

function toggleState() {
        setKeywords(state => [{ ...state, isUsed: !state.isUsed }])
    }

现在我没有收到类型错误,但它仍然没有按预期工作。这是 toggleState 之前的状态:

[
  {
    "name": "State",
    "value": [
      {
        "todo": "Learn React",
        "id": "91bad41d-1561-425a-9e77-960f731d058a",
        "isComplete": false
      }
    ]

这是之后的状态:

[
  {
    "name": "State",
    "value": [
      {
        "0": {
          "todo": "Learn React",
          "id": "91bad41d-1561-425a-9e77-960f731d058a",
          "isComplete": false
        },
        "isComplete": true
      }
    ]

这是我的其余代码:

import React, { useState, useEffect } from 'react'
import { uuid } from 'uuidv4'
import { Form, FormGroup, Input, Button } from 'reactstrap'

function Example(props) {
    const [todos, setTodos] = useState([])

    // Run when component first renders
    useEffect(() => {
        console.log('useEffect component first rendered')
        if (localStorage.getItem('todoData')) {
            setTodos(JSON.parse(localStorage.getItem('todoData')))
        }
    }, [])

    // Run when todos state changes
    useEffect(() => {
        console.log('useEffect todos changed')
        localStorage.setItem('todoData', JSON.stringify(todos))
    }, [todos])

    const [formInput, setFormInput] = useState()

    function handleChange(e) {
        setFormInput(e.target.value)
    }

    function handleSubmit(e) {
        e.preventDefault()
        setTodos(prev => prev.concat({ todo: formInput, id: uuid(), isComplete: false }))
        setFormInput('')
    }

    function toggleState() {
        setTodos(state => [{ ...state, isComplete: !state.isComplete }])
    }

    return (
        <div className='text-center'>
            <div className='mb-2 border text-center' style={{ height: '300px', overflowY: 'scroll' }}>
                {todos.map(todo => (
                    <p className={todo.isUsed ? 'text-success my-1' : 'text-danger my-1'} key={todo.id}>
                        {todo.todo}
                    </p>
                ))}
            </div>
            <Form onSubmit={handleSubmit}>
                <FormGroup>
                    <Input onChange={handleChange} type='text' name='text' id='todoForm' placeholder='Enter a todo' value={formInput || ''} />
                    <Button>Set Todo</Button>
                </FormGroup>
            </Form>
            <Button onClick={toggleState}>Toggle isComplete</Button>
        </div>
    )
}

export default Example

我最终使用的方法,以及我看到其他开发人员使用的方法,是先复制对象或状态,对其进行修改,然后使用修改后的状态设置新状态。

我还注意到您需要为待办事项提供索引才能切换它们,所以我添加了该功能。

看一个工作示例,单击下面的 "运行 代码段"

// main.js

// IGNORE THIS BECAUSE THIS IS JUST TO USE REACT IN STACK OVERFLOW
const { useEffect, useState } = React;

// ---- CODE STARTS HERE -----
const Example = (props) => {
    const [todos, setTodos] = useState([]);
    const [formInput, setFormInput] = useState('');

    // Run when component first renders
    useEffect(() => {
        /*
        // Uncomment - Just doesn't work in Stack Overflow
        if (localStorage && localStorage.getItem('todoData')) {
            setTodos(JSON.parse(localStorage.getItem('todoData')));
        }
        */
    }, []);
    
    // Hooks
    const handleChange = event => {
      setFormInput(event.target.value);
    };
    
    const handleSubmit = event => {
      const newTodosState = [...todos ]; // make copy
      newTodosState.push({ todo: formInput, isComplete: false });
      setTodos(newTodosState);
      
      // Add functionality to update localStorage
      // ex:
      // localStorage.setItem('todoData', newTodosState);
      
      // Reset form
      setFormInput('');
      
      event.preventDefault();
    };
    
    const toggleTodoState = index => event => {
      const newTodosState = [...todos ]; // make copy
      newTodosState[index].isComplete = !newTodosState[index].isComplete;
      setTodos(newTodosState);
      
      // Add functionality to update localStorage
    };
    
    const handleDelete = index => event => {
      const newTodosState = [...todos.slice(0, index), ...todos.slice(index + 1) ];
      setTodos(newTodosState);
      
      // Add functionality to update localStorage
    }
    
    // Render
    return (<div>
      <h3>Todos</h3>
      <ul>
        {todos.map((item, index) => <li key={`todo-${index}`}>{item.todo} - <input type="checkbox" checked={item.isComplete} onClick={toggleTodoState(index)} /> - <button onClick={handleDelete(index)}>Delete</button></li>)}
      </ul>
      <hr />
      <form onSubmit={handleSubmit}>
        <input type="text" value={formInput} onChange={handleChange} placeholder="Enter todo name" />
        <button type="submit">Add</button>
      </form>
    </div>);
};

ReactDOM.render(<Example />, document.querySelector('#root'));
<body>
<div id="root"></div>

<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<script type="text/babel" src="main.js"></script>
</body>

所以在你的具体情况下,你只想在第一个项目上切换 isComplete,可以这样实现:

  function toggleState() {
    setTodos(([firstItem, ...remainder]) => {
      return [
        {
          ...firstItem,
          isComplete: !firstItem.isComplete
        },
        ...remainder
      ];
    });
  }

我们使用解构赋值来获取 FirstItem 并对其进行操作,并将提醒传播回状态。