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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 14:24:56