React结合Material UI开发待办应用时无法单独选中单个复选框
问题根因
- TodoCards组件全局共用单个
checked状态:组件顶层仅声明了1个布尔类型的选中状态,所有循环渲染的复选框都绑定该值,触发任意复选框的修改操作都会同步所有复选框的选中状态。 - 待办数据未存储单条完成状态:
todos数组中的每个任务对象没有单独的completed字段标记对应任务的完成状态,无法实现单条任务的选中状态控制。 - 状态归属错误:
todos数组存储在父组件TodoInput中,修改任务状态的逻辑需放在父组件实现,通过props传递给子组件调用,避免父子组件状态不同步。
修改方案
第一步:修改 TodoInput.js
- 提交任务时为每个新增的todo添加
completed: false的初始状态 - 新增切换任务完成状态的方法,通过props传递给
TodoCards组件 - 移除冗余的
loadedTodos逻辑(该变量未实际参与渲染)
修改后完整代码:
import React, { useState } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import { TextField, Button } from '@material-ui/core'; import { TodoCards } from '../UI/TodoCards'; import { Progress } from '../UI/Progress'; const useStyles = makeStyles((theme) => ({ root: { '& > *': { margin: theme.spacing(1), width: '25ch', textAlign: 'center' }, }, })); export default function TodoInput() { const classes = useStyles(); const [userInput, setUserInput] = useState({ id: '', task: '' }); const [todos, setTodos] = useState([]) // 错误状态 const [error, setError] = useState({ errorMessage: '', error: false }) // 新增切换任务完成状态的方法 const toggleTodo = (id) => { setTodos(todos.map(todo => todo.id === id ? {...todo, completed: !todo.completed} : todo )) } // 提交用户输入的待办 const submitUserInput = (e) => { e.preventDefault(); if (userInput.task === "") { setError({ errorMessage: 'Cannot be blank', error: true }) } else { // 新增的todo默认未完成 setTodos([...todos, {...userInput, completed: false}]) setError({ errorMessage: '', error: false }) // 提交后清空输入框 setUserInput({id: '', task: ''}) } } // 处理用户输入 const handleUserInput = function (e) { setUserInput({ ...userInput, id: Date.now(), // 替换Math.random(),用时间戳作为更可靠的临时id task: e.target.value }) } return ( <div> <Progress taskCount={todos.length} /> <form className={classes.root} noValidate autoComplete="off" onSubmit={submitUserInput}> {error.error ? <TextField id="outlined-error-helper-text" label="Today's task" variant="outlined" type="text" value={userInput.task} onChange={handleUserInput} error={error.error} helperText={error.errorMessage} /> : <TextField id="outlined-basic" label="Today's task" variant="outlined" type="text" value={userInput.task} onChange={handleUserInput} />} <Button variant="contained" color="primary" type="submit">Submit</Button> {todos.length > 0 && <TodoCards taskValue={todos} toggleTodo={toggleTodo} />} </form> </div> ); }
第二步:修改 TodoCards.js
- 移除内部的
checked状态,直接使用每个todo自带的completed属性控制选中状态 - 点击复选框时调用父组件传入的
toggleTodo方法,传入当前任务的id触发状态更新 - 替换数组索引为任务id作为元素的key,避免列表渲染异常
修改后完整代码:
import React from 'react' import { Card, CardContent, Typography, FormControlLabel, Checkbox } from '@material-ui/core'; export const TodoCards = ({ taskValue, toggleTodo }) => { return ( <div> <Card> {taskValue.map((individual) => { return ( <CardContent key={individual.id}> <Typography variant="body1"> <FormControlLabel control={ <Checkbox color="primary" checked={individual.completed} onChange={() => toggleTodo(individual.id)} /> } label={individual.task} /> </Typography> </CardContent> ) })} </Card> </div> ) }
额外优化建议
如果后续需要获取已完成任务列表,直接过滤todos数组即可,无需额外维护数组:
const completedTasks = todos.filter(todo => todo.completed)
内容的提问来源于stack exchange,提问作者sol_s
相关产品推荐
相关产品推荐

