You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React菜谱收藏功能异常求助:无法获取收藏列表并校验重复

问题

开发React菜谱收藏功能,需求是仅当目标菜谱未在用户收藏列表中时,才添加至收藏。目前调用接口获取favourites数组并通过ID校验重复的功能失效,无法获取到收藏数组,console.log输出为空。

RecipeModel组件逻辑

import "./RecipeModel.css";
import { Link, NavLink, useNavigate, useParams } from 'react-router-dom';
import React, { useLayoutEffect, useState,useEffect, useContext } from 'react';
import * as userService from '../../../services/userService';
import { AuthContext } from "../../../contexts/AuthContext";
import * as likeService from '../../../services/likeService';
import useRecipeState from "../../EditRecipe/RecipeState";

const RecipeModel = ({recipe}) => {
    const history = useNavigate();
    const {user} = useContext(AuthContext);
    const {recipeId} = useParams();
    const [likes,setLikes] = useRecipeState(recipeId);
    const [favourites,setFavourites] = useState([]);

    useEffect(() => {
        likeService.getLikes(recipe._id)
            .then(likes => {
                setLikes(state => ({...state, likes}))
            })
    }, []);

    useEffect(() => {
        userService.yourFavourites(user._id)
        .then(result => {
            setFavourites(result);
        })
    },[]);

    const valid = !(favourites.some(i => i._id === recipe._id));

    if(valid) {
        const HandleFavourite = (e) =>  {
            e.preventDefault();
            console.log(`Recipe ${recipe._id}`);
            const name = recipe.name;
            const time = recipe.time;
            const imageUrl = recipe.imageUrl;
            const ingredients = recipe.ingredients;
            const instructions = recipe.instructions;
            
            userService.addFavourite({
                name,
                time,
                imageUrl,
                ingredients,
                instructions
            },user.accessToken)
             .then(result => {
                console.log(result);
                console.log(index);
                console.log(user._id);
                history('/favourites');
            })
        }
    }

    const likeButtonClick = () => {
        if (user._id === recipe._ownerId) {
            return;
        }
        if (recipel.likes.includes(user._id)) {
            return;
        }
        likeService.like(user._id, recipeId)
            .then(() => {
                setLikes(state => ({...state, likes: [...state.likes, user._id]}));
                console.log('liked');
            });
    };

    return (
        <article className="articles">
            <img className="img2" src={recipe.imageUrl}/>
            <h1>{recipe.name}</h1>
            <p className="cut-text">{recipe.instructions}</p>
            <div className="btns1">
            <Link  smooth= "true" className="btnd" to={`/recipe-details/recipe-number:${recipe._id}`}>Details</Link>
            <button className="like" onClick={likeButtonClick} > <i className="fas fa-solid fa-thumbs-up"></i>  {likes.likes?.length || 0}</button>
            <button className="favour" onClick={HandleFavourite} ><i className="fas fa-solid fa-heart-circle-plus"></i></button>
            </div>
        </article>
    );
};

export default RecipeModel;

userService相关逻辑

export const addFavourite = async (recipeData,token) => {
    let response = await fetch(`${baseUrl}/favourites`, {
        method: 'POST',
        headers: {
            'content-type': 'application/json',
            'X-Authorization': token,
        },
        body: JSON.stringify(recipeData)
    });
    let  result = await response.json();
    return result;
};

export const getAllFavourites = async () => {
    let response = await fetch(`${baseUrl}/favourites`)
    let recipes = await response.json();
    let result = Object.values(recipes);
    return result;
}

export const removeFavourite = (recipeId, token) => {
    return fetch(`${baseUrl}/favourites/${recipeId}`, {
        method: 'DELETE',
        headers: {
            'X-Authorization': token,
        },
    }).then(res => res.json());
};

export const getOneFav = (recipeId) => {
    return fetch(`${baseUrl}/favourites/${recipeId}`)
        .then(res => res.json())
};

export const yourFavourites = (ownerId) => {
    let query = encodeURIComponent(`_ownerId="${ownerId}"`);
    return request.get(`${baseUrl}/favourites?where=${query}`);
};

问题排查与解决方案

一、核心问题定位

  1. 接口调用失败:yourFavourites函数中使用未定义的request.get,且未携带用户认证令牌,后端大概率拒绝返回私有数据。
  2. 组件逻辑错误:HandleFavourite定义在if(valid)块内,当菜谱已收藏时函数不存在,点击按钮直接报错;初始favourites为空,异步数据返回后校验逻辑未更新。
  3. 依赖缺失:获取收藏列表的useEffect未添加user依赖,用户切换账号后无法重新请求数据;点赞函数存在笔误recipel.likes。

二、修复方案

1. 修复userService中的yourFavourites函数

替换未定义的request.get为标准fetch,并携带认证令牌:

export const yourFavourites = async (ownerId, token) => {
  let query = encodeURIComponent(`_ownerId="${ownerId}"`);
  const response = await fetch(`${baseUrl}/favourites?where=${query}`, {
    headers: {
      'X-Authorization': token,
    }
  });
  return response.json();
};

2. 修正RecipeModel组件逻辑

import "./RecipeModel.css";
import { Link, useNavigate, useParams } from 'react-router-dom';
import React, { useState, useEffect, useContext } from 'react';
import * as userService from '../../../services/userService';
import { AuthContext } from "../../../contexts/AuthContext";
import * as likeService from '../../../services/likeService';
import useRecipeState from "../../EditRecipe/RecipeState";

const RecipeModel = ({recipe}) => {
    const history = useNavigate();
    const {user} = useContext(AuthContext);
    const {recipeId} = useParams();
    const [likes,setLikes] = useRecipeState(recipeId);
    const [favourites,setFavourites] = useState([]);

    useEffect(() => {
        likeService.getLikes(recipe._id)
            .then(likes => {
                setLikes(state => ({...state, likes}))
            })
    }, [recipe._id]);

    // 修复:添加user依赖,携带token请求,捕获错误
    useEffect(() => {
        if (!user?._id || !user?.accessToken) return;
        userService.yourFavourites(user._id, user.accessToken)
            .then(result => {
                setFavourites(result);
                console.log('收藏列表:', result);
            })
            .catch(err => console.error('获取收藏列表失败:', err));
    }, [user]);

    // 修复:直接定义函数,点击时再做校验
    const HandleFavourite = (e) =>  {
        e.preventDefault();
        const isAlreadyFavourite = favourites.some(i => i._id === recipe._id);
        if (isAlreadyFavourite) return;

        const recipeData = {
            name: recipe.name,
            time: recipe.time,
            imageUrl: recipe.imageUrl,
            ingredients: recipe.ingredients,
            instructions: recipe.instructions
        };
        
        userService.addFavourite(recipeData, user.accessToken)
            .then(result => {
                console.log('添加收藏成功:', result);
                // 本地更新收藏列表,避免刷新页面
                setFavourites(prev => [...prev, result]);
                history('/favourites');
            })
            .catch(err => console.error('添加收藏失败:', err));
    };

    const likeButtonClick = () => {
        if (!user?._id) return;
        if (user._id === recipe._ownerId) return;
        // 修复笔误:recipel -> recipe
        if (recipe.likes?.includes(user._id)) return;

        likeService.like(user._id, recipeId)
            .then(() => {
                setLikes(state => ({...state, likes: [...state.likes, user._id]}));
                console.log('点赞成功');
            })
            .catch(err => console.error('点赞失败:', err));
    };

    // 计算收藏状态,用于按钮交互
    const isFavourite = favourites.some(i => i._id === recipe._id);

    return (
        <article className="articles">
            <img className="img2" src={recipe.imageUrl}/>
            <h1>{recipe.name}</h1>
            <p className="cut-text">{recipe.instructions}</p>
            <div className="btns1">
                <Link smooth="true" className="btnd" to={`/recipe-details/recipe-number:${recipe._id}`}>Details</Link>
                <button className="like" onClick={likeButtonClick} > 
                    <i className="fas fa-solid fa-thumbs-up"></i>  {likes.likes?.length || 0}
                </button>
                {/* 根据收藏状态修改按钮状态 */}
                <button 
                    className={`favour ${isFavourite ? 'active' : ''}`} 
                    onClick={HandleFavourite} 
                    disabled={isFavourite}
                >
                    <i className="fas fa-solid fa-heart-circle-plus"></i>
                    {isFavourite ? '已收藏' : '收藏'}
                </button>
            </div>
        </article>
    );
};

export default RecipeModel;

3. 额外优化建议

  • 接口调用添加错误捕获,避免静默失败;
  • 添加收藏列表加载状态,提升用户体验;
  • 收藏按钮根据状态禁用或修改样式,防止重复点击。

内容的提问来源于stack exchange,提问作者DimitriTfb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 18:16:37