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

React组件函数内console.log显示空数组问题求助

拖拽功能中函数内数组为空的问题解决

问题描述

函数内的console.log('in function', randomMeals)返回空数组,但函数前后的console.log能显示正确值。实现从randomMeals到dropCalendar的拖拽功能时,只有触发页面重渲染后才能成功拖拽。

核心组件(HomepageCalender)代码

import React, { useState } from 'react';
import './HomepageCalender.css';
import { Meal } from '../../types';
import RecipeCard from '../RecipeCard/RecipeCard';
import { useDrop } from 'react-dnd';

interface HomepageCalenderProps {
  randomMeals: Meal[];
  toggleLock: (idMeal: string) => void;
}

interface DroppedMeal {
  id: string;
}

const HomepageCalender: React.FC<HomepageCalenderProps> = ({ randomMeals, toggleLock }) => {
  const [dropCalendar, setDropCalendar] = useState<Meal[]>([]);

  const [{ isOver }, drop] = useDrop(() => ({
    accept: 'recipe-card',
    drop: (droppedMeal: DroppedMeal) => {
      console.log('meal', droppedMeal);
      addRecipeCardToBoard(droppedMeal.id);
      // console.log('hook', randomMeals)
    },
    collect: (monitor) => ({
      isOver: !!monitor.isOver(),
    }),
  }));
  
  console.log('before funtion', randomMeals)
  const addRecipeCardToBoard = (id: string) => {
    console.log('in function', randomMeals)

    const filteredRandomMeals = randomMeals.filter((meal) => meal.idMeal === id);

    const uniqueDroppedMeals = filteredRandomMeals.filter(
      (meal, index, self) =>
      index === self.findIndex((m) => m.idMeal === meal.idMeal)
      );
    
      setDropCalendar((prevDropCalendar) => [...prevDropCalendar, ...uniqueDroppedMeals]);
  };
  console.log('after function', randomMeals)

  return (
    <div className='homepage-calendar-container'>
      <h4 className='date'>Date</h4>
      <div className='droppable-area' ref={drop}>
        {dropCalendar.map((meal) => (
          <RecipeCard
            key={meal.idMeal}
            idMeal={meal.idMeal}
            strMeal={meal.strMeal}
            strMealThumb={meal.strMealThumb}
            toggleLock={toggleLock}
            locked={true}
          />
        ))}
      </div>
    </div>
  );
};

export default HomepageCalender;

父组件(Homepage)代码

import React, { useEffect, useState } from 'react';
import Header from '../Header/Header';
import WelcomeBox from '../WelcomeBox/WelcomeBox';
import Footer from '../Footer/Footer';
import RandomMealForm from '../RandomMealForm/RandomMealForm';
import HomepageCalender from '../HomepageCalender/HomepageCalender';
import { fetchSingleRandomRecipe } from '../../apiCalls';
import { RandomMealProps } from '../../types';

const Homepage: React.FC = () => {
  const [numberOfMeals, setNumberOfMeals] = useState<number>(5);
  const [randomMeals, setRandomMeals] = useState<RandomMealProps[]>([]);

  const toggleLock = (idMeal: string) => {
    const updatedRandomMeals = randomMeals.map((meal) => {
      if (meal.idMeal === idMeal) {
        return { ...meal, locked: !meal.locked }; // Create a new object
      }
      return meal;
    });

    setRandomMeals(updatedRandomMeals);
  };

  useEffect(() => {
    const fetchData = async () => {
      try {
        const fetchedMeals = [];
        for (let i = 0; i < numberOfMeals; i++) {
          const data = await fetchSingleRandomRecipe();
          if (data?.meals) {
            const newMeals = data.meals.map((meal: RandomMealProps) => ({
              ...meal,
              locked: false,
            }));
            fetchedMeals.push(...newMeals);
          }
        }

        const uniqueMeals = fetchedMeals.filter(
          (meal, index, self) =>
            index === self.findIndex((m) => m.idMeal === meal.idMeal)
        );

        setRandomMeals(uniqueMeals);
      } catch (error) {
        console.error(error);
        // Handle errors here if needed
      }
    };

    fetchData();
  }, [numberOfMeals]);

  useEffect(() => {
    console.log('homepage', randomMeals);
  }, [randomMeals]);

  return (
    <div className='homepage'>
      <Header />
      {randomMeals.length > 0 && (
        <WelcomeBox randomMeals={randomMeals} toggleLock={toggleLock} />
      )}
      <RandomMealForm
        numberOfMeals={numberOfMeals}
        setNumberOfMeals={setNumberOfMeals}
        setRandomMeals={setRandomMeals}
        randomMeals={randomMeals}
        toggleLock={toggleLock}
      />
      <HomepageCalender randomMeals={randomMeals} toggleLock={toggleLock} />
      <Footer />
    </div>
  );
};

export default Homepage;

问题原因

  1. 闭包捕获旧值:addRecipeCardToBoard函数在组件初始渲染时创建,捕获了当时的randomMeals(空数组)。后续randomMeals更新时,组件重渲染会创建新的addRecipeCardToBoard,但react-dnd的useDrop回调里引用的还是旧版本的函数,触发拖拽时调用旧函数,拿到的就是空数组。
  2. useDrop未更新依赖:useDrop的工厂函数没有设置依赖数组,导致它不会随着randomMeals或addRecipeCardToBoard的更新而重新创建拖拽逻辑,始终使用初始渲染时的闭包值。

解决方案

方案一:更新useDrop依赖+useCallback包裹函数

修改核心组件代码,确保函数和拖拽逻辑随randomMeals更新:

import React, { useState, useCallback } from 'react';
import './HomepageCalender.css';
import { Meal } from '../../types';
import RecipeCard from '../RecipeCard/RecipeCard';
import { useDrop } from 'react-dnd';

interface HomepageCalenderProps {
  randomMeals: Meal[];
  toggleLock: (idMeal: string) => void;
}

interface DroppedMeal {
  id: string;
}

const HomepageCalender: React.FC<HomepageCalenderProps> = ({ randomMeals, toggleLock }) => {
  const [dropCalendar, setDropCalendar] = useState<Meal[]>([]);

  // 用useCallback包裹,依赖更新时函数重新创建
  const addRecipeCardToBoard = useCallback((id: string) => {
    console.log('in function', randomMeals);
    const filteredRandomMeals = randomMeals.filter((meal) => meal.idMeal === id);
    const uniqueDroppedMeals = filteredRandomMeals.filter(
      (meal, index, self) => index === self.findIndex((m) => m.idMeal === meal.idMeal)
    );
    setDropCalendar((prev) => [...prev, ...uniqueDroppedMeals]);
  }, [randomMeals]);

  const [{ isOver }, drop] = useDrop(() => ({
    accept: 'recipe-card',
    drop: (droppedMeal: DroppedMeal) => {
      console.log('meal', droppedMeal);
      addRecipeCardToBoard(droppedMeal.id);
    },
    collect: (monitor) => ({
      isOver: !!monitor.isOver(),
    }),
  }), [addRecipeCardToBoard]); // 依赖函数,确保拖拽逻辑更新
  
  console.log('before function', randomMeals);
  console.log('after function', randomMeals);

  return (
    <div className='homepage-calendar-container'>
      <h4 className='date'>Date</h4>
      <div className='droppable-area' ref={drop}>
        {dropCalendar.map((meal) => (
          <RecipeCard
            key={meal.idMeal}
            idMeal={meal.idMeal}
            strMeal={meal.strMeal}
            strMealThumb={meal.strMealThumb}
            toggleLock={toggleLock}
            locked={true}
          />
        ))}
      </div>
    </div>
  );
};

export default HomepageCalender;

方案二:直接在drop回调中处理逻辑

如果不需要复用addRecipeCardToBoard,可以直接把逻辑写在drop回调里,同时给useDrop加上randomMeals依赖:

const [{ isOver }, drop] = useDrop(() => ({
  accept: 'recipe-card',
  drop: (droppedMeal: DroppedMeal) => {
    console.log('meal', droppedMeal);
    console.log('in drop', randomMeals);
    const filteredRandomMeals = randomMeals.filter((meal) => meal.idMeal === droppedMeal.id);
    const uniqueDroppedMeals = filteredRandomMeals.filter(
      (meal, index, self) => index === self.findIndex((m) => m.idMeal === meal.idMeal)
    );
    setDropCalendar((prev) => [...prev, ...uniqueDroppedMeals]);
  },
  collect: (monitor) => ({
    isOver: !!monitor.isOver(),
  }),
}), [randomMeals]);

方案三:用useRef保存最新数组

如果不想频繁更新useDrop依赖,可以用ref同步最新的randomMeals:

import React, { useState, useRef, useEffect } from 'react';

// ...其他代码

const HomepageCalender: React.FC<HomepageCalenderProps> = ({ randomMeals, toggleLock }) => {
  const [dropCalendar, setDropCalendar] = useState<Meal[]>([]);
  const latestRandomMeals = useRef<Meal[]>([]);

  // 同步最新数组到ref
  useEffect(() => {
    latestRandomMeals.current = randomMeals;
  }, [randomMeals]);

  const addRecipeCardToBoard = (id: string) => {
    // 从ref取最新值
    const meals = latestRandomMeals.current;
    console.log('in function', meals);
    const filteredRandomMeals = meals.filter((meal) => meal.idMeal === id);
    const uniqueDroppedMeals = filteredRandomMeals.filter(
      (meal, index, self) => index === self.findIndex((m) => m.idMeal === meal.idMeal)
    );
    setDropCalendar((prev) => [...prev, ...uniqueDroppedMeals]);
  };

  const [{ isOver }, drop] = useDrop(() => ({
    accept: 'recipe-card',
    drop: (droppedMeal: DroppedMeal) => {
      console.log('meal', droppedMeal);
      addRecipeCardToBoard(droppedMeal.id);
    },
    collect: (monitor) => ({
      isOver: !!monitor.isOver(),
    }),
  }));

  // ...其他代码
};

说明

方案一和二更符合React状态管理逻辑,可读性更高,优先推荐;方案三适合需要避免频繁更新拖拽逻辑的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 05:35:04