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

React中映射GraphQL对象时报错及重复项问题求助

解决React + GraphQL映射数据时的TypeError与重复渲染问题

问题1:TypeError: Cannot convert undefined or null to object 的根源

你遇到的第一个错误主要来自两个细节问题:

  • 字段名大小写不匹配:你的GraphQL查询请求的是locations(小写l),但代码里错误地写了data.LOCATIONS(大写L),这会导致data.LOCATIONS返回undefined,调用Object.entries()时自然抛出无法转换undefined的错误。
  • 错误的数据处理方式:data.locations本身是一个数组,而Object.entries()是用来处理对象键值对的方法,直接对数组使用这个方法完全不符合场景。

问题2:条目重复渲染的原因

当你改成Object.entries(data.locations).map后错误消失,但出现重复条目,核心原因是:
你在CustomTrip组件里循环渲染<Location />(每一个locations条目对应一个<Location />),而**Location组件内部又独立发起了一次useQuery请求,获取了完整的locations数组并渲染所有选项**。相当于每一个<Location />都输出了完整的选项列表,最终总选项数是 N * N(N是locations的条目数),看起来就是每个条目重复了N次。


完整解决方案

我们需要做两个核心修改:

  1. 在CustomTrip中正确获取并传递locations数据给Location组件,避免重复查询
  2. 让Location组件通过props接收数据,不再独立发起GraphQL请求

修改后的CustomTrip组件

import React, {useContext, useEffect } from 'react'
import { COORDS } from "./coords"
import { useMileDispatch, useMileState } from './MileContext';
import { useQuery } from '@apollo/react-hooks';
import { gql } from 'apollo-boost';
import Location from "./location";

const CustomTrip = () => {
  const {locationOne, locationTwo} = useMileState()
  const dispatch = useMileDispatch()

  async function fetchDistance() {
    const res = await fetch("https://api.mapbox.com/directions-matrix/v1/mapbox/driving/" + locationOne + ";" + locationTwo + "?sources=1&annotations=distance&access_token=[token obscured]")
    const mapBoxObject = await res.json();
    const meters = mapBoxObject.distances[0];
    const miles = (parseInt(meters) * 0.00062137119);
    const roundtrip = 2 * miles;
    dispatch({type: 'totalMilesUpdate', payload: roundtrip.toFixed(2)})
  }

  useEffect(() => {
    fetchDistance()
  }, [locationOne, locationTwo]);

  const LOCATIONS = gql`
  {
    locations {
      id
      slug
      gps
    }
  }
  `;

  const { loading, error, data } = useQuery(LOCATIONS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error :(</p>;

  return (
    <div>
      <h3>Customize your trip</h3>
      Mileage will be calculated as a round trip.
      <br/>
      Select your starting point
      <select value={locationOne} onChange={(event) => dispatch({type: 'locationOneChange', payload: event.target.value})}>
        {/* 直接传递locations数组给Location组件,无需循环 */}
        <Location locations={data.locations} />
      </select>
      Select your destination
      <select value={locationTwo} onChange={(event) => dispatch({type: 'locationTwoChange', payload: event.target.value})}>
        <Location locations={data.locations} />
      </select>
    </div>
  )
};

export default CustomTrip;

修改后的Location组件

import React from 'react'

// 通过props接收locations数据,不再内部发起查询
const Location = ({ locations }) => {
  // 数据未准备好时显示默认选项
  if (!locations) {
    return <option value="-73.778716,42.740913">ALB</option>;
  }

  // 直接对数组进行map渲染选项
  return locations.map(({ slug, gps }) => (
    <option key={slug} value={gps}>
      {slug}
    </option>
  ))
};

export default Location;

额外优化建议

  • 可以在CustomTrip的loading状态下,给下拉框添加加载提示,提升用户体验
  • 保持option的key属性唯一性(你已经做到了,这点很好),避免React列表渲染警告

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 11:47:49