React中useEffect与useState结合Filter使用的问题(疑似渲染循环)
Hey there! Let's break down exactly what's going wrong here and walk through how to fix it.
The Root of the Problem
Your getProducts() function is asynchronous — it returns a Promise, not the actual product array you expect. When you call setProducts(getProducts()) directly in useEffect, you're setting your products state to a Promise object instead of the data from your backend. Later, when you try to run filter on that Promise (which isn't an array), you get the "Cannot filter undefined" error. Plus, if your dependency array isn't set correctly, this can trigger unnecessary re-renders and even render loops.
Step-by-Step Solution
1. Ensure Your Service Function is Correctly Written
First, double-check your Product.js service file to make sure it properly returns the fetched data as a Promise:
// services/Product.js export const getProducts = async () => { try { // Replace with your actual backend endpoint const response = await fetch('/api/products'); if (!response.ok) { throw new Error('Failed to fetch products'); } return await response.json(); } catch (error) { console.error('Fetch error:', error); throw error; // Let the component handle the error } };
2. Update Your Component to Handle Async Logic Properly
In your letter.js component, you need to wait for the Promise to resolve before updating state. You also want to handle loading/error states to avoid rendering issues:
// views/letter.js import { useState, useEffect } from 'react'; import { getProducts } from '../../services/Product'; const LetterView = () => { // Initialize products as an empty array to avoid filter errors upfront const [products, setProducts] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { // Wrap async logic in an inner function (useEffect can't be async directly) const fetchAndSetProducts = async () => { try { setIsLoading(true); // Wait for the async function to resolve and get the actual data const fetchedProducts = await getProducts(); setProducts(fetchedProducts); } catch (err) { setError(err.message); } finally { setIsLoading(false); } }; fetchAndSetProducts(); }, []); // Empty dependency array = run only once on component mount // Handle loading/error states before rendering the product list if (isLoading) return <div>Loading products...</div>; if (error) return <div>Oops: {error}</div>; return ( <div className="product-list"> {/* Now filter is safe to use since products is always an array */} {products.filter(product => product.type === 'letter').map(item => ( <div key={item.id} className="product-item"> {item.name} </div> ))} </div> ); }; export default LetterView;
Key Fixes to Note
- Initialize state with an empty array: This ensures
products.filternever runs onundefinedwhile waiting for the API response. - Wait for the Promise to resolve: Using
awaitongetProducts()ensures you only pass actual data tosetProducts. - Avoid async useEffect callbacks: React warns against async functions directly in
useEffect, so we wrap the logic in an inner async function. - Add loading/error states: This improves user experience and prevents rendering errors during data fetching.
- Set proper dependencies: The empty dependency array ensures the fetch only runs once on mount (no render loops). If you need to refetch when a value changes (e.g., a category filter), add that value to the dependency array.
内容的提问来源于stack exchange,提问作者SERGIO GARCÍA GONZÁLEZ

