React标签页状态更新时组件重置、标签显示异常问题求助
问题描述
我正在React中创建一个类似下图的标签页组件:
需求:只有当前一个组件完成(点击对应按钮)后,后续标签才会显示。
现有代码
import { FaCheckCircle } from "react-icons/fa"; import Inicio from "./Inicio"; import Incluido from "./Incluido"; import Descripcion from "./Descripcion"; import React, { useState } from "react"; import { FaBookOpen } from "react-icons/fa"; import { FaCheckDouble } from "react-icons/fa"; function Home() { const [toggle, setToggle] = useState(0) const [opciones, setOpciones] = useState([{ id: 0, texto: "Inicio", visibilidad: "", descripcionTitulo: "Bienvenido al programa de carga de Viajes", descripcion: "Selecciona lo que quieras hacer", icon: <FaCheckCircle />, next: next, contenido: <Inicio setToggle={setToggle} />, botonSiguiente: "Nuevo Viaje" }, { id: 1, texto: "Descripción", visibilidad: "aa", descripcionTitulo: "Descripcion", descripcion: "detalles importantes", icon: <FaBookOpen />, next: next, contenido: <Descripcion setToggle={setToggle} />, botonSiguiente: "Siguiente" }, { id: 2, texto: "Incluido / no", visibilidad: "hidden", descripcionTitulo: "Incluido/ No incluido", descripcion: "Si el servicio no está seleccionado, el cliente lo verá como no incluido.", icon: <FaCheckDouble />, next: next, contenido: <Incluido setToggle={setToggle} />, botonSiguiente: "Siguiente" } ]) function next(next) { if (opciones[next.id + 1]) { setToggle(next.id + 1) setOpciones(opciones.map(e => (e.id === next.id + 1 ? { ...e, visibilidad: "descripcion 1" } : e))) } } return ( <div className="App min-h-[100vh] grid grid-cols-12 gap-3 flex-row p-5 bg-slate-900"> <aside className='col-span-4 md:col-span-4 lg:col-span-2 min-h-[50%] bg-slate-800 text-start border-l-2 border-slate-600'> <ul className='p-5 text-white font-semibold flex flex-col space-x-5'> {opciones.map((opcion) => ( <div className={opcion.visibilidad} key={opcion.id}> <li className="flex flex-row cursor-pointer mt-1" onClick={() => setToggle(opcion.id)}> <span className='text-xl mr-2'> {opcion.icon} </span> <span> {opcion.texto} </span> </li> </div> ))} </ul> </aside> <section className='col-span-8 md:col-span-8 lg:col-span-10 min-h-[50%] bg-slate-800 p-5'> {opciones.map((opcion) => ( <div key={opcion.id} className={toggle === opcion.id ? "show-content" : "content"}> <div className='border-b text-left border-cyan-400 w-full pb-5'> <h2 className='text-2xl font-semibold text-white'> {opcion.descripcionTitulo} </h2> <span className='text-white'> {opcion.descripcion} </span> </div> {opcion.contenido} <div className='mt-3 flex justify-end w-full'> <button className='bg-cyan-500 text-white font-semibold p-4 rounded' onClick={() => opcion.next(opcion)}> {opcion.botonSiguiente} </button> </div> </div> ))} </section> </div > ); } export default Home;
遇到的问题
点击第一个组件的按钮后,“Descripción”标签会显示,但点击“Descripción”组件的按钮时,该标签又会变回隐藏状态。每次调用next函数时,本该检查并显示下一个组件及对应标签,但每次重新渲染后标签都会回到隐藏状态,不知道该怎么处理。
解决方案
问题核心在于你在useState初始值里给每个选项绑定了next函数,组件重新渲染时next会被重新创建,导致状态里的next是旧版本,调用时引用的opciones也是旧状态值;同时你修改可见性时只更新下一个标签,没有保留已显示标签的状态。
修复步骤
- 移除状态中绑定的
next属性,直接在按钮点击时调用外部定义的next函数,避免闭包问题 - 改用
completedTabs数组记录已完成的标签ID,标签可见性由前序标签是否完成来判断 - 更新状态时使用函数式更新,确保拿到最新的状态值
修复后的代码
import { FaCheckCircle } from "react-icons/fa"; import Inicio from "./Inicio"; import Incluido from "./Incluido"; import Descripcion from "./Descripcion"; import React, { useState } from "react"; import { FaBookOpen } from "react-icons/fa"; import { FaCheckDouble } from "react-icons/fa"; function Home() { const [toggle, setToggle] = useState(0); // 记录已完成的标签ID,初始默认第一个标签完成 const [completedTabs, setCompletedTabs] = useState([0]); // 静态选项数据移出useState,避免不必要的状态更新 const opciones = [ { id: 0, texto: "Inicio", descripcionTitulo: "Bienvenido al programa de carga de Viajes", descripcion: "Selecciona lo que quieras hacer", icon: <FaCheckCircle />, contenido: <Inicio setToggle={setToggle} />, botonSiguiente: "Nuevo Viaje" }, { id: 1, texto: "Descripción", descripcionTitulo: "Descripcion", descripcion: "detalles importantes", icon: <FaBookOpen />, contenido: <Descripcion setToggle={setToggle} />, botonSiguiente: "Siguiente" }, { id: 2, texto: "Incluido / no", descripcionTitulo: "Incluido/ No incluido", descripcion: "Si el servicio no está seleccionado, el cliente lo verá como no incluido.", icon: <FaCheckDouble />, contenido: <Incluido setToggle={setToggle} />, botonSiguiente: "Siguiente" } ]; function next(currentTab) { const nextTabId = currentTab.id + 1; if (opciones[nextTabId]) { setToggle(nextTabId); // 将下一个标签标记为已完成,避免重复添加 if (!completedTabs.includes(nextTabId)) { setCompletedTabs(prev => [...prev, nextTabId]); } } } return ( <div className="App min-h-[100vh] grid grid-cols-12 gap-3 flex-row p-5 bg-slate-900"> <aside className='col-span-4 md:col-span-4 lg:col-span-2 min-h-[50%] bg-slate-800 text-start border-l-2 border-slate-600'> <ul className='p-5 text-white font-semibold flex flex-col space-x-5'> {opciones.map((opcion) => { // 可见性判断:第一个标签默认显示,后续标签需前一个已完成才显示 const isVisible = opcion.id === 0 || completedTabs.includes(opcion.id - 1); return ( <div key={opcion.id} className={isVisible ? "" : "hidden"}> <li className="flex flex-row cursor-pointer mt-1" onClick={() => setToggle(opcion.id)}> <span className='text-xl mr-2'> {opcion.icon} </span> <span> {opcion.texto} </span> </li> </div> ); })} </ul> </aside> <section className='col-span-8 md:col-span-8 lg:col-span-10 min-h-[50%] bg-slate-800 p-5'> {opciones.map((opcion) => ( <div key={opcion.id} className={toggle === opcion.id ? "show-content" : "hidden"}> <div className='border-b text-left border-cyan-400 w-full pb-5'> <h2 className='text-2xl font-semibold text-white'> {opcion.descripcionTitulo} </h2> <span className='text-white'> {opcion.descripcion} </span> </div> {opcion.contenido} <div className='mt-3 flex justify-end w-full'> <button className='bg-cyan-500 text-white font-semibold p-4 rounded' onClick={() => next(opcion)}> {opcion.botonSiguiente} </button> </div> </div> ))} </section> </div> ); } export default Home;
关键说明
- 静态选项数据移出
useState,减少不必要的状态更新 - 用
completedTabs数组持久化记录已完成的标签,确保已显示的标签不会在重新渲染时变回隐藏 - 可见性逻辑改为基于前序标签的完成状态,符合“完成一个才显示下一个”的需求
- 移除状态中绑定的
next函数,避免闭包导致的旧状态引用问题
内容的提问来源于stack exchange,提问作者Annie
相关产品推荐
相关产品推荐

