为什么我的 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 并对其进行操作,并将提醒传播回状态。
我整天都被困在这个问题上,希望能得到一些帮助。提前致谢。
起初我是这样写的,但我会得到一个类型错误: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 并对其进行操作,并将提醒传播回状态。