Chakra UI中map渲染复选框无法单独取消选中的解决方法
问题根源
现有代码存在3个逻辑错误,直接导致子复选框无法单独操作:
- 选中状态初始值为写死的固定长度数组
[false, false],和接口动态返回的用户列表长度不匹配 - 遍历渲染子复选框时,所有子项的选中值都绑定到
checkedItems[0],状态更新时也只修改数组第一个元素,所有子项状态完全同步,无法独立变更 - 父复选框全选逻辑写死仅操作2个状态位,无法适配动态长度的列表
- 隐藏问题:分页切换时未重置选中状态,翻页后会出现状态和当前页用户数据错位的情况
修复方案
- 调整选中状态存储结构,用用户唯一ID作为键存储选中状态,避免列表顺序变化、分页导致的状态错位(列表固定无分页的场景也可以用遍历索引做关联)
- 页内用户数据加载完成、分页切换时,初始化当前页用户的默认选中状态
- 父复选框触发全选/全不选时,遍历当前页所有用户生成对应长度的状态集合
- 单个子复选框变更时,仅更新对应当前用户的状态位,不改动其他子项状态
- 按需保留跨页选中记忆能力,不需要的话翻页直接重置状态即可
修复后完整代码
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
相关产品推荐
相关产品推荐

