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

React渲染select选项报对象不可作为React子元素错误排查

问题根因
  • 触发Objects are not valid as a React child报错的直接原因是JSX中存在错误写法:<div className='titre'> Saisissez votre ville {ListVille}</div>。你把状态变量ListVille直接插入到文本节点中,当接口返回值更新状态后,React尝试直接渲染对象/数组类型的变量到DOM,直接触发该报错,和setState异步特性无关。
  • 你观察到的setList调用后立刻打印ListVille仍为空是React状态更新的正常表现:React状态更新是异步批量执行的,新状态值只会在组件下一次重渲染时生效,不需要额外写特殊逻辑处理这个异步过程,只要做好渲染阶段的类型兜底即可。
  • 现有渲染逻辑存在边界漏洞:三目判断没有覆盖初始空数组、接口返回空值、请求异常等场景,强行访问ListVille[0].key时会因为数组为空、元素不存在触发类型错误。
  • 请求触发逻辑存在性能问题:把fetch写在input的onChange回调中,每输入一个字符就会发起一次请求,既产生大量无效请求,还可能出现旧请求晚返回、覆盖新请求结果的顺序错乱问题。
修复步骤
  1. 删除文本节点中直接渲染ListVille的错误写法,永远不要把对象/数组类型的状态直接作为文本子节点渲染。
  2. 统一ListVille的类型约定:初始值为空数组,接口返回非数组值、无匹配结果、请求报错时,都将ListVille重置为空数组,从根源上消除多分支类型判断的复杂度。
  3. 把请求逻辑迁移到useEffect中,监听邮编输入值的变化,只有当输入长度符合邮编规则(比如法国邮编为5位)时才发起请求,减少无效请求。
  4. 简化select选项的渲染逻辑:空数组调用map不会生成任何节点,天然符合「无匹配结果时只显示默认Choisissez选项」的预期,不需要额外写非数组场景的判断分支,同时给遍历生成的option加唯一key属性消除列表警告。
  5. 给fetch请求加错误捕获,避免接口异常时页面崩溃。
修复后完整代码
import React, { useState, useEffect } from 'react';
import { Navigate } from 'react-router-dom'
import Entete from './MEP/entete'

function Localisation() {
  const [CP, setCP] = useState('');
  const [city, setCity] = useState('');
  // 约定ListVille永远是数组类型,初始为空数组
  const [ListVille, setList] = useState([]);
  const [goToExploit, setGoToExploit] = useState(false);
  // 可选:加加载状态优化体验
  const [loading, setLoading] = useState(false);

  function handleSubmit(event) {
    event.preventDefault()
    setGoToExploit(true)
  }

  if(goToExploit) {
      return <Navigate push to={`/exploitation`} />
  }

  // 监听CP变化触发请求,替代onChange里直接发请求的写法
  useEffect(() => {
    // 邮编长度不足5位时,直接清空城镇列表,不发请求
    if (CP.length !== 5) {
      setList([]);
      return;
    }

    const controller = new AbortController();
    const fetchData = async () => {
      setLoading(true);
      try {
        const res = await fetch(`http://localhost:3000/api/1.0/getCommunes/${CP}`, {
          signal: controller.signal
        });
        const data = await res.json();
        // 确保存入状态的永远是数组,非数组/无结果时存空数组
        setList(Array.isArray(data) ? data : []);
      } catch (err) {
        // 请求被取消/报错时清空列表,避免脏数据
        if (err.name !== 'AbortError') {
          console.error('获取城镇列表失败:', err);
          setList([]);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchData();
    // 组件卸载/CP变化时取消上一次未完成的请求,避免请求顺序错乱
    return () => controller.abort();
  }, [CP]);

  function handleChange(event) {
    // onChange只做输入值更新,不处理请求逻辑
    setCP(event.target.value.trim());
  }

  return (
    <div>
      <Entete titre="Localisation"/>

      <form onSubmit={handleSubmit}>
        <div className='titre'> Saisissez votre code postal </div>
        <input 
          className='input'
          value={CP}
          onChange={handleChange}
          placeholder='Code Postal'
          type='text'
          maxLength={5}
          required
        />
        <div className='titre'> Saisissez votre ville </div>
        <select value={city} onChange={(e) => setCity(e.target.value)}>
          <option value="">Choisissez</option>
          {loading && <option value="" disabled>Chargement...</option>}
          {/* ListVille永远是数组,直接map即可,空数组不会生成多余节点 */}
          {ListVille.map((item) => (
            <option key={item.key} value={item.key}>
              {item.libelle}
            </option>
          ))}
        </select>
        <div className='centrer'>
          <button type='submit' className='validation' disabled={!city}> Valider </button>
        </div>
      </form>
    </div>
  );
}

export default Localisation;
额外说明

如果请求时出现跨域报错,需要在你本地3000端口的API服务中配置CORS规则,允许React项目所在的前端端口(通常是5173或3000)跨域访问即可。


内容的提问来源于stack exchange,提问作者Aliénor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 22:09:17