Formik表单提交后如何处理axios响应数据并在MUI卡片展示?
问题描述
我正在开发一个大学小型项目,需要创建Formik表单,输入水果名称后通过axios.get调用fruityvice API获取数据。要求在表单提交时执行请求,返回包含水果名称和营养信息的对象,供主页面(App.js)通过MUI卡片展示。但目前无法实现该功能,始终无法正确处理API返回的数据。
我的代码
import React, { useState } from 'react'; import { Formik, Form, Field, useFormik } from 'formik'; // import axios from 'axios'; import * as yup from "yup"; import { useTranslation } from 'react-i18next'; import { Button, Card, CardContent, TextField, Typography } from '@mui/material'; const API_URL = 'https://www.fruityvice.com/api'; const axios = require('axios'); function FormikMaterialUI({ fruits, totalWeight, setFruits, setTotalWeight }) { const { t } = useTranslation(); const formik = useFormik({ initialValues: { fruitName: '', fruitWeight: 0, }, validationSchema: yup.object({ fruitName: yup.string() .max(100, t('invalidName')) .required(t('requiredField')), fruitWeight: yup.number() .min(1, t('invalidWeight')), }), onSubmit: (values) => { const weight = values.fruitWeight; const fruit = values.fruitName; if (totalWeight + weight <= 400) { fetchFruitData(fruit) .then((newFruit) => { setFruits([...fruits, newFruit]); setTotalWeight(totalWeight + weight); formik.resetForm(); }) .catch((error) => { console.error(error); }); } } }); const fetchFruitData = async (fruit) => { try { const response = await axios.get(`${API_URL}/fruit/${fruit}`); const { name, nutritions } = response.data; const newFruit = { name, nutritions }; return newFruit; } catch (error) { console.error(error); throw error; } }; return ( <div> <form onSubmit={formik.handleSubmit}> <div style={{ display: "grid", gridTemplateColumns: "repeat(1, 2fr)", gridGap: "16px" }}> <TextField id="fruitName" name="fruitName" label={t('fruitName')} type="text" onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.fruitName} error={formik.touched.fruitName && Boolean(formik.errors.fruitName)} helperText={formik.touched.fruitName && formik.errors.fruitName} style={{ margin: "8px 16px", backgroundColor: "peachpuff" }} /> <TextField id="fruitWeight" name="fruitWeight" label={t('weightGrams')} type="number" onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.fruitWeight} error={formik.touched.fruitWeight && Boolean(formik.errors.fruitWeight)} helperText={formik.touched.fruitWeight && formik.errors.fruitWeight} style={{ margin: "8px 16px", backgroundColor: "peachpuff" }} /> </div> <div style={{ display: "flex", justifyContent: "center" }}> <Button type="submit" variant="contained" style={{ margin: "0 auto", backgroundColor: "green" }}> {t('addFruit')} </Button> </div> </form> <p>{totalWeight}</p> </div> ); } export default FormikMaterialUI;
解决方案
1. 修复API响应结构处理问题
fruityvice API的/fruit/{name}接口在匹配到多个相似水果时会返回数组而非单个对象,直接解构name和nutritions会导致报错。修改fetchFruitData函数适配两种返回格式:
const fetchFruitData = async (fruit) => { try { const response = await axios.get(`${API_URL}/fruit/${fruit}`); // 处理数组/对象两种返回格式,取第一个匹配项 const fruitData = Array.isArray(response.data) ? response.data[0] : response.data; // 带上用户输入的重量,确保后续卡片能展示完整信息 return { name: fruitData.name, nutritions: fruitData.nutritions, weight: formik.values.fruitWeight }; } catch (error) { console.error('获取水果数据失败:', error); throw error; } };
2. 解决状态更新的闭包陷阱
onSubmit中直接使用fruits和totalWeight会因闭包获取旧状态值,改用函数式更新确保拿到最新状态:
onSubmit: (values) => { const weight = Number(values.fruitWeight); const fruit = values.fruitName.trim(); if (totalWeight + weight <= 400) { fetchFruitData(fruit) .then((newFruit) => { // 函数式更新获取最新状态 setFruits(prevFruits => [...prevFruits, newFruit]); setTotalWeight(prevTotal => prevTotal + weight); formik.resetForm(); }) .catch((error) => { console.error(error); }); } }
3. 修正输入值类型转换
fruitWeight的输入值为字符串类型,需转为数字后再进行计算,避免出现字符串拼接导致的重量错误。
4. 优化导入方式
替换CommonJS风格的axios导入为ES6标准导入:
import axios from 'axios';
5. 添加用户友好的错误提示
在组件内增加错误状态管理,告知用户API请求失败的原因:
// 组件内新增状态 const [apiError, setApiError] = useState(''); // 修改fetchFruitData的catch逻辑 catch (error) { console.error('获取水果数据失败:', error); setApiError('无法获取该水果数据,请检查名称是否正确'); throw error; } // 在return中添加错误提示组件 {apiError && ( <Typography color="error" align="center" style={{ marginTop: 16 }}> {apiError} </Typography> )}
内容的提问来源于stack exchange,提问作者Cheehee
相关产品推荐
相关产品推荐

