React项目页面刷新后出现TypeError: Cannot read properties of undefined (reading 'includes')错误求助
Hey there! Let's break down what's causing that error when you refresh the page, and fix it up step by step.
The Root Cause
When your component first mounts (like after a page refresh), your user state from useReducer starts as an empty array []—but user data from your API is an object, not an array. On the initial render, before your useEffect finishes fetching the user profile, you try to access user.favourites on that empty array. Since arrays don't have a favourites property, this returns undefined, and calling .includes() on undefined throws the TypeError you're seeing.
Step 1: Fix the Initial User State
First, update your useReducer initial state to use an empty object instead of an empty array—this matches the shape of the user data your API returns:
const [{ loading, user }, dispatch] = useReducer(reducer, { loading: false, user: {} });
Step 2: Add Guard Clauses & Loading State
Next, add checks to make sure you don't try to access user.favourites until the user data is loaded. You can use optional chaining (?.) for a quick check, or handle the loading state explicitly:
Option A: Optional Chaining (Quick Fix)
Modify the heart icon condition to use ?. to safely access favourites:
{user.favourites?.includes(product._id) ? ( <i className="fas fa-heart fa-lg card-icon-btm"></i> ) : ( <span onClick={likesHandler}> <i className="far fa-heart fa-lg card-icon-btm"></i> </span> )}
Option B: Explicit Loading State (Better UX)
Show a loading indicator while the user data is being fetched, so users don't see a broken UI:
if (loading) { return <div>Loading...</div>; } // If user data isn't loaded yet, return a fallback or nothing if (!user || Object.keys(user).length === 0) { return null; } // Rest of your return statement goes here
Step 3: Fix the likesHandler Logic
Your current likesHandler has a bug where you directly mutate the favourites state array with favourites.push(product._id)—this doesn't work with React's state management. Also, you're calling setFavourites twice with conflicting logic. Let's clean that up:
const likesHandler = async(e) => { e.preventDefault(); // Create a new array with the product ID added (avoid mutating state directly) const updatedFavourites = [...favourites, product._id]; setFavourites(updatedFavourites); // Send the updated favourites to your API try { await axios.put( '/api/users/likes', { productId: product._id }, // Send just the product ID instead of the whole array { headers: { Authorization: `Bearer ${userInfo.token}` } } ); // After successful API call, refetch the user profile to sync state fetchUser(); } catch(err) { toast.error(getError(err)); // Revert the state if the API call fails setFavourites(favourites); } };
Step 4: Optimize Your API Endpoint
Your current PUT /api/users/likes endpoint pushes the first element of req.body.favourites every time, which can lead to duplicate entries. Let's update it to check if the product is already in favourites before adding it:
userRouter.put( '/likes', isAuth, expressAsyncHandler(async(req, res) => { const user = await User.findById(req.user._id); if (user) { // Only add the product if it's not already in favourites if (!user.favourites.includes(req.body.productId)) { user.favourites.push(req.body.productId); await user.save(); res.send({ message: 'Favourite added' }); } else { res.send({ message: 'Product already in favourites' }); } } else { res.status(404).send({ message: 'User not found' }); } }) );
Step 5: Sync Local State with API Data
After fetching the user profile in useEffect, update your local favourites state to match the user's actual favourites from the API—this ensures local state is in sync with the database after a refresh:
useEffect(() => { const fetchUser = async () => { dispatch({type: 'FETCHUSER_REQUEST'}); try { const user = await axios.get( '/api/users/profile', { headers: {Authorization: `Bearer ${userInfo.token}`} } ); dispatch({type: 'FETCHUSER_SUCCESS', payload: user.data}); // Update local favourites state with the user's saved favourites setFavourites(user.data.favourites || []); } catch(err) { dispatch({type: 'FETCHUSER_FAIL'}); toast.error(getError(err)); } } fetchUser(); }, []);
Final Notes
- Don't forget to use
classNameinstead ofclassin JSX (you hadclass="fas fa-heart..."—this should beclassName). - Adding error handling in
likesHandlerensures that if the API call fails, your local state doesn't get out of sync with the database.
That should fix the refresh error and make your like button work reliably!
内容的提问来源于stack exchange,提问作者Okay

