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

时间:2020-06-28 03:45:27

标签: javascript reactjs react-hooks react-state-management use-state

我整天都被困在这里,希望得到一些帮助。预先感谢。

起初我是这样写的,但是会出现一个类型错误: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

2 个答案:

答案 0 :(得分:1)

我最终使用的方法(已经见过其他开发人员)是先复制对象或状态,对其进行修改,然后使用修改后的状态设置新状态。

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

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

A
// 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'));

答案 1 :(得分:1)

因此,在您的特定情况下,您只想在第一项上切换isComplete,可以这样实现:

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

在其中使用解构分配来获取FirstItem并对其进行操作,然后将提醒传播回状态。