React同一函数内可切换多个useState吗?附ReadMore功能bug排查
问题根因
三个直接导致异常的核心问题:
- 核心错误:
ReadMore组件被定义在UsersMap父组件内部。每次父组件因状态更新触发重渲染时,ReadMore的组件引用会被重新创建,React会判定这是全新组件,直接卸载旧实例、挂载新实例,导致组件内部的isReadMore永远被重置为初始值true,无法正常切换。 - 状态设计缺陷:
isExpanded是定义在父组件的全局单一状态,所有列表卡片共用这一个状态,点击任意卡片的「read more」会同步影响所有卡片的样式,无法实现单卡独立展开/收起。 - CSS样式问题:
.users和.expand两个类都写死了固定height:25%,没有给展开态设置自适应高度规则,容器高度被锁死时自然会出现文本溢出。
修复方案
按以下步骤调整即可解决所有异常:
- 将
ReadMore组件挪到UsersMap组件外部定义,避免父组件重渲染导致子组件被强制重置。 - 取消全局单一的
isExpanded状态,用卡片id做key存储每张卡的独立展开状态,避免多卡状态联动。 - 调整CSS规则:收起态给固定高度+溢出隐藏,展开态移除固定高度,让容器随内容自适应撑开。
修复后可直接运行的代码
JSX部分
import React from 'react'; import useFetch from '../useFetch.js'; import {RestaurantBox} from '../styles/bestratedrestaurants.js' import {Image,NameBox, TopBox, P, P2,BottomBox} from "../styles/reviewsmap.js" import TestPic from "../assets/london-restaurant.jpeg"; import {Bio,Box,Span} from "../styles/usersMap.js" import {useState} from 'react'; import '../styles/usermap.css' // ReadMore挪到父组件外部定义,避免重渲染时组件被重建 const ReadMore = ({ children, onToggle }) => { const text = children; const [isReadMore, setIsReadMore] = useState(true); const toggleReadMore = () => { setIsReadMore(!isReadMore); // 通知父组件切换当前卡片的展开类名 onToggle(!isReadMore); }; return ( <p className="text"> {isReadMore ? text.slice(0, 150) : text} <Span onClick={toggleReadMore}> {isReadMore ? "...read more" : " show less"} </Span> </p> ); }; const UsersMap = () => { const {data,isPending, error} = useFetch('http://127.0.0.1:8000/backend/api/users/list/'); // 用对象存储每个卡片的独立展开状态,key为卡片id const [expandedMap, setExpandedMap] = useState({}); // 切换单张卡片的展开状态 const toggleCardExpand = (cardId, isExpanded) => { setExpandedMap(prev => ({ ...prev, [cardId]: isExpanded })); }; return ( <RestaurantBox> {error && <div>{error}</div>} {isPending && <div>Loading...</div>} {data && data.map(item => ( <div className={ expandedMap[item.id] ? 'expand': 'users' } key={item.id} > <TopBox> <Image src={TestPic}></Image> <NameBox> <P>{item.first_name} {item.last_name[0]}.</P> <P2>6 Reviews in total</P2> </NameBox> </TopBox> <BottomBox> <Bio> <ReadMore onToggle={(isExpanded) => toggleCardExpand(item.id, isExpanded)}> {item.description} </ReadMore> </Bio> </BottomBox> </div> ))} </RestaurantBox> ); } export default UsersMap
CSS部分
.users { margin-top:20px; width:18%; /* 收起态固定高度 */ height:300px; min-width:300px; border-radius:3px; background-color: #FFFFFF; border-top:5px solid #E47D31; margin-bottom:25px; /* 收起态隐藏溢出内容 */ overflow: hidden; } .expand{ margin-top:20px; width:18%; /* 展开态不设固定高度,随内容自适应 */ min-height:300px; min-width:300px; border-radius:3px; background-color: #FFFFFF; border-top:5px solid #E47D31; margin-bottom:25px; /* 展开态允许内容完全展示 */ overflow: visible; }
修复效果说明
- 挪出
ReadMore组件后,组件内部的isReadMore状态不会再因为父组件重渲染被重置,点击切换时可以正常在true/false间切换,文本截断/完整展示逻辑恢复正常。 - 用
expandedMap存储每张卡片的独立展开状态后,点击单张卡片的按钮只会修改当前卡片的类名,不会影响其他卡片。 - CSS调整后,收起态固定高度隐藏溢出,展开态自适应高度,不会再出现文本溢出容器的问题。
内容的提问来源于stack exchange,提问作者Curtis Sanderson
相关产品推荐
相关产品推荐

