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

Chakra UI中map渲染复选框无法单独取消选中的解决方法

问题根源

现有代码存在3个逻辑错误,直接导致子复选框无法单独操作:

  • 选中状态初始值为写死的固定长度数组[false, false],和接口动态返回的用户列表长度不匹配
  • 遍历渲染子复选框时,所有子项的选中值都绑定到checkedItems[0],状态更新时也只修改数组第一个元素,所有子项状态完全同步,无法独立变更
  • 父复选框全选逻辑写死仅操作2个状态位,无法适配动态长度的列表
  • 隐藏问题:分页切换时未重置选中状态,翻页后会出现状态和当前页用户数据错位的情况
修复方案
  1. 调整选中状态存储结构,用用户唯一ID作为键存储选中状态,避免列表顺序变化、分页导致的状态错位(列表固定无分页的场景也可以用遍历索引做关联)
  2. 页内用户数据加载完成、分页切换时,初始化当前页用户的默认选中状态
  3. 父复选框触发全选/全不选时,遍历当前页所有用户生成对应长度的状态集合
  4. 单个子复选框变更时,仅更新对应当前用户的状态位,不改动其他子项状态
  5. 按需保留跨页选中记忆能力,不需要的话翻页直接重置状态即可
修复后完整代码
import NextLink from 'next/link';
import { useState, useEffect } from 'react';
import { queryClient } from '../../services/queryClient';
import { api } from '../../services/api';
// 补全项目中已有的Chakra组件、自定义组件引入即可
export default function UserList() {
  const [page, setPage] = useState(1);
  // 改为对象结构存储选中状态,key为用户ID,value为布尔值标记是否选中
  const [checkedItems, setCheckedItems] = useState<Record<string, boolean>>({});

  const { data, isLoading, isFetching, error } = useUsers(page);
  
  // 当前页数据加载完成后初始化选中状态
  useEffect(() => {
    if (data?.users) {
      const initialChecked = data.users.reduce((acc, user) => {
        acc[user.id] = false;
        return acc;
      }, {} as Record<string, boolean>);
      setCheckedItems(initialChecked);
    }
  }, [data?.users]);

  // 计算全选、半选状态
  const checkedValues = Object.values(checkedItems);
  const allChecked = checkedValues.length > 0 && checkedValues.every(Boolean);
  const isIndeterminate = checkedValues.some(Boolean) && !allChecked;

  async function handlePrefetchUser(userId: string) {
    await queryClient.prefetchQuery(
      ['user', userId],
      async () => {
        const response = await api.get(`users/${userId}`);
        return response.data;
      },
      {
        staleTime: 1000 * 60 * 10,
      }
    );
  }

  return (
    <Box>
      <Header />
      <Flex my="6" maxWidth={1480} mx="auto" px="6">
        <Sidebar />
        <Box flex="1" borderRadius={8} bg="gray.800" p="8">
          <Flex mb="8" justify="space-between" align="center">
            <Heading size="lg" fontWeight="normal">
              Alunos
              {!isLoading && isFetching && (
                <Spinner size="sm" color="gray.500" ml="4" />
              )}
            </Heading>
            <HStack spacing={4}>
              <NextLink href="/users/create" passHref>
                <Button as="a" size="sm" fontSize="sm" colorScheme="orange">
                  <Icon as={RiAddLine} fontSize="20" />
                </Button>
              </NextLink>
              <NotificationModal />
              <Button
                as="a"
                size="sm"
                fontSize="sm"
                colorScheme="green"
                cursor="pointer"
              >
                <Icon as={CgImport} />
              </Button>
            </HStack>
          </Flex>

          {isLoading ? (
            <Flex justify="center" align="center">
              <Spinner />
            </Flex>
          ) : error ? (
            <Flex justify="center">
              <Text>Falha ao obter dados dos usuários.</Text>
            </Flex>
          ) : (
            <>
              <Table colorScheme="whiteAlpha">
                <Thead>
                  <Tr>
                    <Th px={['4', '4', '6']} color="gray.300" w="8">
                      <Checkbox
                        colorScheme="orange"
                        isChecked={allChecked}
                        isIndeterminate={isIndeterminate}
                        onChange={(e) => {
                          // 全选时遍历当前页所有用户设置统一状态
                          const newChecked = data.users.reduce((acc, user) => {
                            acc[user.id] = e.target.checked;
                            return acc;
                          }, {} as Record<string, boolean>);
                          setCheckedItems(newChecked);
                        }}
                      />
                    </Th>
                    <Th>Usuários</Th>
                    <Th>Ações</Th>
                    <Th w="8"></Th>
                  </Tr>
                </Thead>

                <Tbody>
                  {data.users.map((user) => (
                    <Tr key={user.id}>
                      <Td px={['4', '4', '6']}>
                        <Checkbox
                          colorScheme="orange"
                          // 绑定当前用户对应的选中状态
                          isChecked={!!checkedItems[user.id]}
                          onChange={(e) => {
                            // 仅更新当前用户的选中状态,不影响其他子项
                            setCheckedItems(prev => ({
                              ...prev,
                              [user.id]: e.target.checked
                            }));
                          }}
                        />
                      </Td>
                      <Td>
                        <Box>
                          <Link
                            color="orange.400"
                            onMouseEnter={() => handlePrefetchUser(user.id)}
                          >
                            <Text fontWeight="bold">{user.name}</Text>
                          </Link>
                          <Text fontSize="sm" color="gray.300">
                            {user.email}
                          </Text>
                        </Box>
                      </Td>
                      <Td>
                        <Box cursor="pointer">
                          <HStack spacing={4}>
                            <Text
                              color="orange.400"
                              _hover={{
                                color: 'orange.500',
                              }}
                            >
                              Editar
                            </Text>
                            <Text
                              color="gray.300"
                              _hover={{
                                color: 'gray.500',
                              }}
                            >
                              Excluir
                            </Text>
                          </HStack>
                        </Box>
                      </Td>
                    </Tr>
                  ))}
                </Tbody>
              </Table>

              <Pagination
                totalCountOfRegisters={data.totalCount}
                currentPage={page}
                onPageChange={setPage}
              />
            </>
          )}
        </Box>
      </Flex>
    </Box>
  );
}

内容的提问来源于stack exchange,提问作者jayker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 17:43:14