React中无法访问WeatherAPI响应对象的二级current子对象问题
问题修复方案
核心问题说明
- React 组件保留属性冲突:你给
Display组件传递的key是 React 内置的保留属性,仅用于框架内部的列表节点 diff,不会传递到组件的props中,导致接口请求无法正常获取密钥,请求失败。需修改为自定义属性名如apiKey。 - useEffect 回调规则错误:React 要求
useEffect的回调函数只能返回清理函数或无返回值,直接传入async函数会返回 Promise 对象,就是你收到的[object Promise]报错的直接诱因。 - 状态存储范围不足:你当前仅将接口返回的
location字段存入weatherInfo状态,自然无法访问同层级的current字段,需要存储完整的接口返回结果。 - 嵌套对象渲染错误:
current字段下还包含二级嵌套的condition对象,直接遍历对象值渲染时,React 不允许直接把对象作为子元素渲染,会触发Objects are not valid as a React child报错。 - 搜索逻辑缺失:Navbar 中的搜索按钮点击直接触发
setPlaceName,会将事件对象当成地点名称传入,同时表单默认提交行为会导致页面刷新,无法正常查询目标地点天气。
修复后代码
App.js
import React,{useState} from 'react'; import Navbar from './Navbar.js'; import Display from './Display.js' function App() { const[placeName,setPlaceName] = useState('Raipur') // 注意替换为你自己的接口密钥 let apiKey = 'not gonna tell'; return ( <> <Navbar setPlaceName={setPlaceName} /> {/* 把key改为自定义属性apiKey,避免和React保留属性冲突 */} <Display apiKey={apiKey} placeName={placeName} /> </> ); } export default App;
Navbar.js
import React, { useState } from 'react'; function Navbar(props) { // 新增搜索框输入值状态 const [searchVal, setSearchVal] = useState(''); // 处理搜索提交 const handleSearch = (e) => { // 阻止表单默认提交刷新页面的行为 e.preventDefault(); if(searchVal.trim()){ props.setPlaceName(searchVal.trim()); } } return( <> <nav className="navbar navbar-expand-lg navbar-dark bg-dark"> <div className="container-fluid"> <a className="navbar-brand" href="/">Navbar</a> <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-target="/navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span className="navbar-toggler-icon"></span> </button> <div className="collapse navbar-collapse" id="navbarSupportedContent"> <ul className="navbar-nav me-auto mb-2 mb-lg-0"> <li className="nav-item"> <a className="nav-link active" aria-current="page" href="/">Home</a> </li> <li className="nav-item"> <a className="nav-link" href="/">Link</a> </li> <li className="nav-item dropdown"> <a className="nav-link dropdown-toggle" href="/" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> Dropdown </a> <ul className="dropdown-menu" aria-labelledby="navbarDropdown"> <li><a className="dropdown-item" href="/">Action</a></li> <li><a className="dropdown-item" href="/">Another action</a></li> <li><hr className="dropdown-divider"/></li> <li><a className="dropdown-item" href="/">Something else here</a></li> </ul> </li> <li className="nav-item"> <a className="nav-link disabled">Disabled</a> </li> </ul> <form className="d-flex" onSubmit={handleSearch}> <input className="form-control me-2" type="search" placeholder="搜索地点" aria-label="Search" value={searchVal} onChange={(e)=>setSearchVal(e.target.value)} /> <button className="btn btn-outline-success" type="submit">搜索</button> </form> </div> </div> </nav> </> ); } export default Navbar;
Display.js
import React,{useState,useEffect} from 'react'; function Display(props) { // 初始状态改为对象,对应接口返回的完整结构 const[weatherInfo,setWeatherInfo] = useState({}); // 加载状态,避免接口返回前渲染空数据 const[loading,setLoading] = useState(true); const getWeatherInfo = async () =>{ setLoading(true); try { let url =`https://api.weatherapi.com/v1/current.json?key=${props.apiKey}&q=${props.placeName}&aqi=no`; let res = await fetch(url); let parsedRes = await res.json(); // 存储完整的接口返回结果,包含location和current setWeatherInfo(parsedRes); } catch (err) { console.error('天气接口请求失败:', err); } finally { setLoading(false); } } useEffect(() =>{ // 不要给useEffect的回调加async,内部调用异步函数即可 getWeatherInfo(); // 依赖项加入placeName,切换地点时自动重新查询 },[props.placeName, props.apiKey]) // 扁平化处理对象,避免嵌套对象无法直接渲染 const flattenObj = (obj) => { let result = {}; for(const i in obj){ if(typeof obj[i] === 'object' && !Array.isArray(obj[i])){ const temp = flattenObj(obj[i]); for(const j in temp){ result[`${i}.${j}`] = temp[j]; } } else { result[i] = obj[i]; } } return result; } if(loading) return <div className="container mt-3">加载中...</div>; if(!weatherInfo.location || !weatherInfo.current) return <div className="container mt-3">未查询到对应地点天气信息</div>; // 合并location和current的字段用于渲染,你也可以分开渲染两个模块 const displayData = {...flattenObj(weatherInfo.location), ...flattenObj(weatherInfo.current)}; return ( <> <div className="container mt-3"> <h3>{weatherInfo.location.name} 实时天气</h3> <div className="row mt-3"> {Object.entries(displayData).map(([key, value])=>{ return( <div className="col-md-3 col-sm-6 mb-2" key={key}> <strong>{key}:</strong>{value} </div> ) })} </div> </div> </> ) } export default Display;
内容的提问来源于stack exchange,提问作者arsh coding
相关产品推荐
相关产品推荐

