You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React结合Material UI开发待办应用时无法单独选中单个复选框

问题根因

  • TodoCards组件全局共用单个checked状态:组件顶层仅声明了1个布尔类型的选中状态,所有循环渲染的复选框都绑定该值,触发任意复选框的修改操作都会同步所有复选框的选中状态。
  • 待办数据未存储单条完成状态:todos数组中的每个任务对象没有单独的completed字段标记对应任务的完成状态,无法实现单条任务的选中状态控制。
  • 状态归属错误:todos数组存储在父组件TodoInput中,修改任务状态的逻辑需放在父组件实现,通过props传递给子组件调用,避免父子组件状态不同步。

修改方案

第一步:修改 TodoInput.js

  1. 提交任务时为每个新增的todo添加completed: false的初始状态
  2. 新增切换任务完成状态的方法,通过props传递给TodoCards组件
  3. 移除冗余的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

  1. 移除内部的checked状态,直接使用每个todo自带的completed属性控制选中状态
  2. 点击复选框时调用父组件传入的toggleTodo方法,传入当前任务的id触发状态更新
  3. 替换数组索引为任务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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 15:54:01