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

NextJS中向Tailwind Select添加元素并渲染时持续报错

解决NextJS中Select组件动态数据与默认选中问题

错误原因分析

直接在组件函数体中执行teams.push(newData)和setSelected会触发无限渲染循环:组件渲染→执行修改数组和状态更新代码→状态更新导致组件重新渲染→再次执行修改和更新代码,最终触发React的无限重渲染限制。

具体解决步骤

  1. 创建包含自定义选项的新数组(不修改原数据)
    不要直接修改SWR返回的teams数组,通过useMemo创建包含"Personal"选项的新数组,避免每次渲染重复创建:

    const combinedTeams = useMemo(() => {
      // 把自定义选项放在数组头部
      return [{ id: 1, team_name: 'Personal' }, ...teams];
    }, [teams]); // 仅当teams变化时重新计算
    
  2. 用useEffect设置默认选中值
    依赖combinedTeams,仅当数据加载完成后设置默认选中项,避免重复触发:

    useEffect(() => {
      // 确保数据存在时再设置选中值
      if (combinedTeams) {
        setSelected('Personal');
      }
    }, [combinedTeams]);
    
  3. 修改Listbox渲染逻辑
    渲染选项时使用combinedTeams,同时保持Listbox.Option的value与selected状态类型一致(这里都是字符串team_name)。

修改后的完整组件代码

import { useState, useEffect, useMemo, Fragment } from 'react';
import { Listbox, Transition } from '@headlessui/react';
import { ChevronUpDownIcon, CheckIcon } from '@heroicons/react/20/solid';
import classNames from 'classnames';
import useSWR from 'swr';
import axios from 'axios';
import LoadPageSpinner from './LoadPageSpinner'; // 替换为你的加载组件路径

const Create = () => {
  const [selected, setSelected] = useState('');

  const { data: teams, error, mutate } = useSWR('/teams', () => 
    axios.get('/teams')
      .then(response => response.data.data)
  );

  if (!teams) {
    return (<LoadPageSpinner />);
  }

  // 合并自定义选项与接口返回的teams数据
  const combinedTeams = useMemo(() => {
    return [{ id: 1, team_name: 'Personal' }, ...teams];
  }, [teams]);

  // 设置默认选中项
  useEffect(() => {
    setSelected('Personal');
  }, [combinedTeams]);

  return (
    <>
      <Listbox value={selected} onChange={setSelected}>
        {({ open }) => (
          <>
            <Listbox.Label className="block text-sm font-medium leading-6 text-gray-900">Assigned to</Listbox.Label>
            <div className="relative mt-2">
              <Listbox.Button className="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6">
                <span className="block truncate">{selected}</span>
                <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
                  <ChevronUpDownIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
                </span>
              </Listbox.Button>

              <Transition
                show={open}
                as={Fragment}
                leave="transition ease-in duration-100"
                leaveFrom="opacity-100"
                leaveTo="opacity-0"
              >
                <Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
                  {combinedTeams.map((team) => (
                    <Listbox.Option
                      key={team.id}
                      className={({ active }) =>
                        classNames(
                          active ? 'bg-indigo-600 text-white' : 'text-gray-900',
                          'relative cursor-default select-none py-2 pl-3 pr-9'
                        )
                      }
                      value={team.team_name}
                    >
                      {({ selected, active }) => (
                        <>
                          <span className={classNames(selected ? 'font-semibold' : 'font-normal', 'block truncate')}>
                            {team.team_name}
                          </span>

                          {selected ? (
                            <span
                              className={classNames(
                                active ? 'text-white' : 'text-indigo-600',
                                'absolute inset-y-0 right-0 flex items-center pr-4'
                              )}
                            >
                              <CheckIcon className="h-5 w-5" aria-hidden="true" />
                            </span>
                          ) : null}
                        </>
                      )}
                    </Listbox.Option>
                  ))}
                </Listbox.Options>
              </Transition>
            </div>
          </>
        )}
      </Listbox>
    </>
  )
};

export default Create;

额外注意点

  • 不要直接修改SWR返回的原始数据,保持数据不可变性是React状态管理的核心原则。
  • useMemo用于缓存合并后的数组,避免每次渲染都重新创建新数组,提升性能。
  • 确保Listbox的value与Listbox.Option的value类型完全一致(这里都是字符串类型的team_name),否则选中状态会失效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 15:37:11