React(Ts)多复选框筛选失效,调用Edamam API报400错误
我在用Edamam API获取食谱,单个筛选条件能正常工作,但选择多个时会触发Axios 400错误。
错误信息
react_devtools_backend_compact.js:13851 Error fetching recipes:
AxiosError {message: 'Request failed with status code 400', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {…}, request: XMLHttpRequest, …}
code:
"ERR_BAD_REQUEST"
config:
{transitional: {…}, adapter: Array(3), transformRequest: Array(1),
transformResponse: Array(1), timeout: 0, …}
message:
"Request failed with status code 400"
name:
"AxiosError"
request:
XMLHttpRequest {onreadystatechange: null, readyState: 4, timeout: 0,
withCredentials: false, upload: XMLHttpRequestUpload, …}
response:
{data: {…}, status: 400, statusText: '', headers: AxiosHeaders, config: {…}, …}
相关代码
components/SearchBar.tsx
"use client" import PrimaryButton from '@/components/PrimaryButton'; import { Box, Button, Card, CardContent, TextField, Typography } from '@mui/material'; import React, { useState } from 'react'; interface SearchBarProps { onSearch: (query: string , filters: { diet: string[] }) => void; } const SearchBar: React.FC<SearchBarProps> = ({ onSearch }) => { const [query, setQuery] = useState(''); const [error, setError] = useState(''); // Filter state const [dietFilter, setDietFilter] = useState<string[]>([]); const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); if (!query.trim()) { setError('Search query must not be null or empty'); return; } setError(''); // Clear error if query is valid console.log("Search query:", query); // Debug statement onSearch(query, { diet: dietFilter }); }; const handleDietChange = (event: React.ChangeEvent<HTMLInputElement>) => { const { value, checked } = event.target; setDietFilter((prevDietFilter) => checked ? [...prevDietFilter, value] : prevDietFilter.filter((diet) => diet !== value) ); }; return ( <> <Box display="flex" justifyContent="center" alignItems="center" minHeight="80vh"> <Card sx={{ maxWidth: 500, width: '100%', p: 2 }}> <CardContent> <Typography variant="h6" component="h6" gutterBottom> Find the recipe </Typography> <Box mt={2} display="flex" alignItems="center"> <form onSubmit={handleSubmit}> <TextField type="text" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search for recipes" variant="outlined" fullWidth sx={{ mb: 2 }} error={Boolean(error)} // Show error state helperText={error} // Display the error message /> <Box mb={2}> <Typography variant="subtitle1">Diet</Typography> <label> <input type="checkbox" value="low-carb" onChange={handleDietChange} /> Low-Carb </label> <label style={{ marginLeft: '10px' }}> <input type="checkbox" value="high-protein" onChange={handleDietChange} /> High-Protein </label> </Box> <PrimaryButton type="submit" variant="contained">Search</PrimaryButton> </form> </Box> </CardContent> </Card> </Box> </> ); }; export default SearchBar;
api.tsx
const API_URL = 'https://api.edamam.com/api/recipes/v2'; const APP_ID = process.env.NEXT_PUBLIC_APP_ID; const APP_KEY = process.env.NEXT_PUBLIC_APP_KEY; export const fetchRecipes = async (query: string, filters: FetchRecipesFilters = {}): Promise<Recipe[]> => { if (!query) { throw new Error('Search query must not be null or empty'); } try { // Debug: Log the query and filters console.log('Query:', query); console.log('Filters:', filters); const params: Record<string, string> = { type: 'public', q: query, app_id: APP_ID!, app_key: APP_KEY!, }; // If diet filters are provided, add them to the params if (filters.diet && filters.diet.length > 0) { params.diet = filters.diet.join(','); // Join filters with a comma } // Debug: Log the params being sent to the API console.log('Request Params:', params); const response = await axios.get<ApiResponse>(API_URL, { params, }); if (!response.data.hits) { throw new Error('No recipes found'); } return response.data.hits.map((hit) => hit.recipe); } catch (error) { console.error('Error fetching recipes:', error); return []; } };
SearchContent.tsx
interface RecipeProp { uri: string; label: string; image: string; images: Images; ingredientLines: string[]; } export default function RecipeSearchContent() { const [recipes, setRecipes] = React.useState<RecipeProp[]>([]); const [isMounted, setIsMounted] = useState(true); useEffect(() => { setIsMounted(true); return () => { setIsMounted(false); }; }, []); const handleSearch = useCallback(async (query: string , filters: FetchRecipesFilters) => { try { // Fetch recipes from the API const results: RecipeProp[] = await fetchRecipes(query, filters); // If the component is still mounted, update the state if (isMounted) { setRecipes(results); } } catch (error) { console.error("Error fetching recipes:", error); } }, [isMounted]); return ( <Container> <SearchBar onSearch={handleSearch} /> <RecipeListContent recipes={recipes} /> </Container> ); }
问题原因及修复
Edamam API的多diet筛选条件不支持逗号拼接的单参数格式,而是需要重复传递diet参数(每个筛选条件对应一个diet=xxx)。当前代码将多个diet值用逗号拼接成单个参数,不符合API要求,导致400错误。
修改api.tsx中的参数处理逻辑:
- 调整params的类型,允许数组值
- 直接将diet数组赋值给params.diet,axios会自动转换为多个同名参数
修改后的关键代码:
// 调整params类型,支持数组值 const params: Record<string, string | string[]> = { type: 'public', q: query, app_id: APP_ID!, app_key: APP_KEY!, }; // 处理diet筛选条件 if (filters.diet && filters.diet.length > 0) { params.diet = filters.diet; // 直接传递数组,axios自动生成多个diet参数 }
修改后,请求参数会变为diet=low-carb&diet=high-protein,符合Edamam API的要求,即可解决400错误。
内容的提问来源于stack exchange,提问作者user17449555

