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次。
完整解决方案
我们需要做两个核心修改:
- 在
CustomTrip中正确获取并传递locations数据给Location组件,避免重复查询 - 让
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
相关产品推荐
相关产品推荐

