React天气应用如何通过同一表单提交触发双API请求并管理状态
问题原因
你现在的代码有两个核心问题导致功能无法正常联动:
- 状态混用:把「用户输入的城市名称」和「接口返回的城市图片URL」存在同一个
city状态中,图片请求成功后会直接把原本存储的城市名覆盖为图片链接,后续天气请求无法拿到正确的城市查询参数 - 提交逻辑拆分:表单提交事件、按钮点击事件分别只绑定了单个接口请求函数,触发提交时只会执行其中一个请求,无法同时拉取两类数据;且按钮重复绑定提交事件还可能造成重复请求问题
修复步骤
- 拆分独立状态,按数据用途分开存储,避免互相覆盖
- 用
searchCity存储用户在输入框输入的城市名称 - 用
bgImageUrl存储Unsplash接口返回的城市背景图地址 - 用
weatherInfo存储天气接口返回的温度、风速等详情数据,初始值设为null
- 用
- 写统一的表单提交处理函数,提交时并行触发两个接口请求(两个接口无前后依赖,用
Promise.all并行请求可以缩短加载等待时间) - 移除搜索按钮上多余的
onClick绑定,只保留表单的onSubmit事件绑定统一处理函数即可,按钮设置type="submit"会自动触发表单提交 - 页面底部预留区域渲染
weatherInfo里存储的天气数据即可
修复后完整代码
import { useState } from 'react'; import axios from 'axios'; import { BsSearch } from 'react-icons/bs'; function App() { // 拆分三个独立状态,各司其职 const [searchCity, setSearchCity] = useState(""); const [bgImageUrl, setBgImageUrl] = useState(""); const [weatherInfo, setWeatherInfo] = useState(null); const [loading, setLoading] = useState(false); // 统一提交处理函数 const handleSearchSubmit = async (e) => { e.preventDefault(); if (!searchCity.trim()) return; setLoading(true); try { // 并行发起两个接口请求,减少等待时间 const [imageRes, weatherRes] = await Promise.all([ axios.get(`https://api.unsplash.com/photos/random?query=${searchCity}&client_id={API_KEY}`), axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${searchCity}&units=metric&appid=${OTHER_API_KEY}`) ]); // 分别更新对应状态 setBgImageUrl(imageRes.data.urls.regular); setWeatherInfo(weatherRes.data); } catch (err) { console.error("请求失败:", err); } finally { setLoading(false); } }; return ( <div className="App"> {/* HEADER */} <div className="bg-black w-full h-[600px] relative"> <img src={bgImageUrl} alt="city image" className="w-full h-[600px] object-cover" /> {/* Search and Info */} <div className="absolute w-full h-[600px] top-[0%] flex flex-col justify-center bg-gradient-to-t from-black"> <div className="flex justify-center py-4"> <h1 className="font-bold text-white text-3xl">Weather App</h1> </div> <div className="flex justify-center"> <form onSubmit={handleSearchSubmit} className="flex justify-between border border-white rounded-xl max-w-[300px] p-4 w-full" > <input onChange={(e) => setSearchCity(e.target.value)} value={searchCity} className="bg-transparent focus:outline-none placeholder:text-white text-white w-full mr-2" type="text" placeholder="Search a City..." disabled={loading} /> <button type="submit" disabled={loading} className="text-white"> <BsSearch size={20} /> </button> </form> </div> </div> </div> {/* 天气数据渲染区域 */} <div className="p-6 max-w-4xl mx-auto"> {loading && <p className="text-center">加载中...</p>} {weatherInfo && !loading && ( <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-6"> <div className="p-4 border rounded-lg"> <p className="text-gray-500">城市</p> <p className="text-2xl font-bold">{weatherInfo.name}</p> </div> <div className="p-4 border rounded-lg"> <p className="text-gray-500">温度</p> <p className="text-2xl font-bold">{Math.round(weatherInfo.main.temp)}°C</p> </div> <div className="p-4 border rounded-lg"> <p className="text-gray-500">风速</p> <p className="text-2xl font-bold">{weatherInfo.wind.speed} m/s</p> </div> <div className="p-4 border rounded-lg"> <p className="text-gray-500">天气</p> <p className="text-2xl font-bold">{weatherInfo.weather[0].main}</p> </div> </div> )} </div> </div> ); } export default App;
额外优化点
- 给输入框加了受控绑定
value={searchCity},避免输入值和状态不同步 - 增加了loading状态,请求过程中禁用输入框和按钮,避免重复提交
- 加了空输入校验,用户没输入内容时不会发无效请求
- 加了基础的错误捕获,接口报错时不会直接导致页面崩溃
- 补了天气数据的基础渲染示例,你可以根据接口返回的字段自行调整展示样式
内容的提问来源于stack exchange,提问作者6ixDeveloper
相关产品推荐
相关产品推荐

