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

React+Bootstrap分页组件切换页面时表单值未正确刷新

试题分页组件中Checkbox/Radio状态异常问题

背景

我用React做了个试题分页组件,试题来自不同的测试组件,部分题用checkbox或单选按钮作答。Test组件里定义了试题数组dataTest,传给Paginacion分页组件,每页展示5道题。

代码示例

Test组件代码

const dataTest = [
  <>
    <div className="row">
      <div className="col-12 col-md-9 col-lg-10 col-xl-10 col-xxl-10"><span>Question 1</span></div>
      <div className="col-3 col-md-1 col-lg-1 col-xl-1">
        <div className="form-check">
          <input id="formCheck-1" className="form-check-input" type="checkbox" />
          <label className="form-check-label" htmlFor="formCheck-1">Yes</label>
        </div>
      </div>
      <div className="col-3 col-md-1 col-lg-1 col-xl-1">
        <div className="form-check">
          <input id="formCheck-2" className="form-check-input" type="checkbox" />
          <label className="form-check-label" htmlFor="formCheck-2">No</label>
        </div>
      </div>
    </div>
  </>,
  // Question 2到Question 9的结构和上面完全一致,此处省略重复代码
  <>
    <div className="row">
      <div className="col-12 col-md-9 col-lg-10 col-xl-10 col-xxl-10"><span>Question 9</span></div>
      <div className="col-3 col-md-1 col-lg-1 col-xl-1">
        <div className="form-check">
          <input id="formCheck-1" className="form-check-input" type="checkbox" />
          <label className="form-check-label" htmlFor="formCheck-1">Yes</label>
        </div>
      </div>
      <div className="col-3 col-md-1 col-lg-1 col-xl-1">
        <div className="form-check">
          <input id="formCheck-2" className="form-check-input" type="checkbox" />
          <label className="form-check-label" htmlFor="formCheck-2">No</label>
        </div>
      </div>
    </div>
  </>,
]

return (
  <>
    <Paginacion elementsList={dataTest} currentPagePar={0} rangePagesPar={5} /> {/* 每页5道题,默认显示第0页 */}
  </>
)

Paginacion分页组件代码

export const Paginacion = ({elementsList, currentPagePar, rangePagesPar}) => {

  const [fullList, setFullList] = useState(elementsList)
  const [currentPage, setCurrentPage] = useState(currentPagePar)
  const [rangePages, setRangePages] = useState(rangePagesPar) // 每页展示数量

  const changePage = (page) => {
    setCurrentPage(page);
  }

  useEffect(() =>{
    console.log(currentPage * rangePages);
    console.log((currentPage * rangePages) + rangePages);
  }, [currentPage]);

  return (
    <>
      {
        // 根据当前页码截取对应试题渲染
        fullList.slice(currentPage * rangePages, (currentPage * rangePages) + rangePages).map((current) => (
          <>
            {current}
          </>
        ))
      }
      <nav>
        <ul className="pagination">
          {
            (() => {
              // 生成分页按钮
              let valD = fullList.length;
              let totalPages = valD / rangePages;
              if (valD%rangePages !=0) totalPages += 1;
              const retArray = [];
              retArray.push(
                <li key="init" className="page-item">
                  <a className="page-link" aria-label="Previous" href="#">
                    <span aria-hidden="true">«</span>
                  </a>
                </li>
              );
              // 此处重复累加,导致总页数计算错误
              if (valD%rangePages !=0) totalPages += 1;
              for (let index = 0; index < totalPages; index ++){                            
                retArray.push(
                  <li key={index} className="page-item">
                    <a className="page-link" href="#" onClick={() => changePage(index)}>{index}</a>
                  </li>                                
                );
              }
              retArray.push(
                <li key="end" className="page-item">
                  <a className="page-link" aria-label="Next" href="#">
                    <span aria-hidden="true">»</span>
                  </a>
                </li>
              );
              return retArray;
            })()
          }
        </ul>
      </nav>
    </>
  )
}

问题现象

切换页面时,试题文本能正常更新,但checkbox/单选按钮的勾选状态异常:要么保留上一页的选择结果,要么给列表项添加唯一key后,切换页面直接清空所有选择,问题仍未解决。添加key的代码如下:

fullList.slice(currentPage * rangePages, (currentPage * rangePages) + rangePages).map((current, i) => (
  <>
    <li key={(currentPage * rangePages) + i}>
      {current}
    </li>
  </>
))

问题根源

  1. ID重复冲突:所有试题的checkbox都使用formCheck-1和formCheck-2,DOM中ID必须全局唯一,导致浏览器无法正确关联label与input,状态逻辑混乱。
  2. 未受控组件缺陷:checkbox未绑定React状态,完全由DOM自身维护状态,分页切换时React复用DOM元素,即使添加了key,重复的ID仍会导致状态残留或被清空。
  3. 分页计算错误:原代码中重复执行totalPages += 1,导致总页数计算偏差,分页逻辑本身存在问题。

修复方案

1. 为每个input生成唯一ID

给每个checkbox的ID添加试题索引后缀,确保全局唯一,比如第1题的Yes按钮用formCheck-1-yes。

2. 使用受控组件管理状态

将所有试题的答题状态统一维护在父组件的state中,通过props传递给子组件,确保切换页面时状态能正确匹配当前页试题。

3. 修正分页计算逻辑

用Math.ceil直接计算总页数,避免重复累加的错误。

修正后的Test组件

import { useState } from 'react';
import { Paginacion } from './Paginacion';

const Test = () => {
  // 初始化答题状态:9道题,每道题的Yes/No默认未勾选
  const [answers, setAnswers] = useState(Array(9).fill({ yes: false, no: false }));

  // 处理checkbox勾选变化
  const handleCheckboxChange = (questionIndex, type) => {
    setAnswers(prev => {
      const newAnswers = [...prev];
      newAnswers[questionIndex] = {
        ...newAnswers[questionIndex],
        [type]: !newAnswers[questionIndex][type]
      };
      return newAnswers;
    });
  };

  // 生成带唯一ID和受控状态的试题数组
  const dataTest = Array.from({ length: 9 }, (_, index) => (
    <>
      <div className="row mb-3">
        <div className="col-12 col-md-9 col-lg-10 col-xl-10 col-xxl-10"><span>Question {index + 1}</span></div>
        <div className="col-3 col-md-1 col-lg-1 col-xl-1">
          <div className="form-check">
            <input
              id={`formCheck-${index + 1}-yes`}
              className="form-check-input"
              type="checkbox"
              checked={answers[index].yes}
              onChange={() => handleCheckboxChange(index, 'yes')}
            />
            <label className="form-check-label" htmlFor={`formCheck-${index + 1}-yes`}>Yes</label>
          </div>
        </div>
        <div className="col-3 col-md-1 col-lg-1 col-xl-1">
          <div className="form-check">
            <input
              id={`formCheck-${index + 1}-no`}
              className="form-check-input"
              type="checkbox"
              checked={answers[index].no}
              onChange={() => handleCheckboxChange(index, 'no')}
            />
            <label className="form-check-label" htmlFor={`formCheck-${index + 1}-no`}>No</label>
          </div>
        </div>
      </div>
    </>
  ));

  return (
    <>
      <Paginacion elementsList={dataTest} currentPagePar={0} rangePagesPar={5} />
    </>
  );
};

export default Test;

修正后的Paginacion组件

import { useState } from 'react';

export const Paginacion = ({ elementsList, currentPagePar, rangePagesPar }) => {
  const [fullList, setFullList] = useState(elementsList);
  const [currentPage, setCurrentPage] = useState(currentPagePar);
  const [rangePages, setRangePages] = useState(rangePagesPar);

  const changePage = (page) => {
    setCurrentPage(page);
  };

  return (
    <>
      {fullList
        .slice(currentPage * rangePages, (currentPage * rangePages) + rangePages)
        .map((current, i) => (
          // 用试题的全局索引作为key,确保唯一且稳定
          <div key={(currentPage * rangePages) + i}>
            {current}
          </div>
        ))}
      <nav>
        <ul className="pagination">
          {(() => {
            const valD = fullList.length;
            const totalPages = Math.ceil(valD / rangePages);
            const retArray = [];

            // 上一页按钮,当前页为第一页时禁用
            retArray.push(
              <li key="prev" className={`page-item ${currentPage === 0 ? 'disabled' : ''}`}>
                <a
                  className="page-link"
                  aria-label="Previous"
                  href="#"
                  onClick={(e) => {
                    e.preventDefault();
                    if (currentPage > 0) changePage(currentPage - 1);
                  }}
                >
                  <span aria-hidden="true">«</span>
                </a>
              </li>
            );

            // 生成页码按钮,当前页高亮显示
            for (let index = 0; index < totalPages; index++) {
              retArray.push(
                <li key={index} className={`page-item ${currentPage === index ? 'active' : ''}`}>
                  <a
                    className="page-link"
                    href="#"
                    onClick={(e) => {
                      e.preventDefault();
                      changePage(index);
                    }}
                  >
                    {index + 1} {/* 页码从1开始更符合用户习惯 */}
                  </a>
                </li>
              );
            }

            // 下一页按钮,当前页为最后一页时禁用
            retArray.push(
              <li key="next" className={`page-item ${currentPage === totalPages - 1 ? 'disabled' : ''}`}>
                <a
                  className="page-link"
                  aria-label="Next"
                  href="#"
                  onClick={(e) => {
                    e.preventDefault();
                    if (currentPage < totalPages - 1) changePage(currentPage + 1);
                  }}
                >
                  <span aria-hidden="true">»</span>
                </a>
              </li>
            );

            return retArray;
          })()}
        </ul>
      </nav>
    </>
  );
};

修复后效果

  • 切换页面时,checkbox状态会正确匹配当前页试题,不会残留上一页选择或清空状态。
  • 分页按钮逻辑正常,第一页和最后一页的上下页按钮自动禁用,当前页高亮显示。
  • 所有input的ID唯一,label与input正确关联,点击label可直接勾选对应checkbox。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 10:10:34