首頁  >  問答  >  主體

將一個函數作為屬性傳遞給兩個元件,但在目標元件中傳回為undefined

import React, { useContext, useState } from 'react';
import Main from '../components/Main';
import Row from '../components/Row';
import requests from '../requests';
import AppContext from '../lib/AuthContext';

const Home = () => {

  const [likedItems, setLikedItems] = useState([]);

  const contextValue = useContext(AppContext);
  const token = window.localStorage.getItem('trailerflix-jwt');

  const handleNewLikes = item => {
    setLikedItems(prevLikedItems => [...prevLikedItems, item]);
  };

  if (token && contextValue.user?.user) {
    return (
      <>
        <Main handleNewLikes={handleNewLikes} />
        <Row rowId='0' title={`${(token && contextValue.user?.user) ? contextValue?.user?.user.username : null}'s List`} fetchURL='/auth/get-likes'
        likedItems={likedItems} handleNewLikes={handleNewLikes} />
        <Row rowId='1' title='Top 10 Movies in the U.S. Today' fetchURL={requests.popular} />
        <Row rowId='2' title='Coming Soon' fetchURL={requests.upcoming} />
        <Row rowId='3' title='Trending Now' fetchURL={requests.trending} />
        <Row rowId='4' title='Now Playing in Theaters' fetchURL={requests.nowPlaying} />
        <Row rowId='5' title='Animation' fetchURL={requests.animation} />
        <Row rowId='6' title='Horror' fetchURL={requests.horror} />
        <Row rowId='7' title='Comedy' fetchURL={requests.comedy} />
      </>
    );
  } else {
    return (
      <>
        <Main />
        <Row rowId='1' title='Top 10 Movies in the U.S. Today' fetchURL={requests.popular} />
        <Row rowId='2' title='Coming Soon' fetchURL={requests.upcoming} />
        <Row rowId='3' title='Trending Now' fetchURL={requests.trending} />
        <Row rowId='4' title='Now Playing in Theaters' fetchURL={requests.nowPlaying} />
        <Row rowId='5' title='Animation' fetchURL={requests.animation} />
        <Row rowId='6' title='Horror' fetchURL={requests.horror} />
        <Row rowId='7' title='Comedy' fetchURL={requests.comedy} />
      </>
    );
  }

};

export default Home;

所以這是父元件,我從這裡將 handleNewLikes 函數作為 prop 傳遞給 Row。

import axios from 'axios';
import React, { useEffect, useState, useRef } from 'react';
import Media from './Media';
import { MdChevronLeft, MdChevronRight } from 'react-icons/md';

const Row = ({ title, fetchURL, rowId, videos, likedItems, handleNewLikes }) => {

  const [media, setMedia] = useState([]);

  useEffect(() => {
    const token = window.localStorage.getItem('trailerflix-jwt');
    if (token && fetchURL === '/auth/get-likes') {
      axios.get(fetchURL, {
        headers: {
          'Content-Type': 'application/json',
          'X-Access-Token': `${token}`
        }
      })
        .then(res => {
          const flattenedArray = res.data.map(item => item.favoritedItem);
          const newestLikesFirst = flattenedArray.reverse();
          setMedia(newestLikesFirst);
        })
        .catch(error => {
          console.error('Axios GET request failed:', error);
        });
    } else if (fetchURL !== '/auth/get-likes') {
      axios.get(fetchURL)
        .then(response => {
          setMedia(response.data.results);
        })
        .catch(error => {
          console.error('Axios GET request failed:', error);
        });
    }
  }, [fetchURL, likedItems]);

  const validMedia = [];

  for (let i = 0; i < media.length; i++) {
    if (media[i].backdrop_path !== null) {
      validMedia.push(media[i]);
    } else {
      media.splice(i, 1);
    }
  }

  const rowRef = useRef(null);
  const [isMoved, setIsMoved] = useState(false);

  const handleSlider = direction => {
    setIsMoved(true);

    if (rowRef.current) {
      const { scrollLeft, clientWidth } = rowRef.current;

      const scrollTo =
        direction === 'left' ? scrollLeft - clientWidth : scrollLeft + clientWidth;

      rowRef.current.scrollTo({ left: scrollTo, behavior: 'smooth' });
    }

  };

  return (

    <>
      <h2 className='text-white font-bold md:text-2xl p-4 mt-8 mb-3 ml-4'>{title}</h2>
      <div className='relative flex items-center group mb-10 ml-4'>

        <MdChevronLeft className={`text-white bg-transparent left-0 absolute hover:opacity-100 cursor-pointer z-10 hidden invisible lg:visible md:visible group-hover:block ${!isMoved && ' lg:invisible md:invisible'}`} size={60}
          onClick={() => handleSlider('left')}
        />
        <div id={'slider' + rowId} className='w-full h-full overflow-x-scroll whitespace-nowrap scroll-smooth relative scrollbar-hide overflow-y-hidden' ref={rowRef}>
          {validMedia.map((item, id) => {
            return <Media key={id} item={item} rowId={rowId} handleNewLikes={handleNewLikes} likedItems={likedItems}/>;
          })}
        </div>
        <MdChevronRight className='text-white bg-transparent right-0 absolute hover:opacity-100 cursor-pointer z-10 hidden invisible lg:visible md:visible group-hover:block' size={60}
          onClick={() => handleSlider('right')}
        />
      </div>
    </>
  );
};

export default Row;

這是行組件,在其中我將handleNewLikes 函數傳遞給媒體。

import React, { useState, useContext, useEffect } from 'react';
import axios from 'axios';
import { FaHeart, FaRegHeart } from 'react-icons/fa';
import Player from './Player';
import AppContext from '../lib/AuthContext';

const Media = ({ item, rowId, handleNewLikes, likedItems }) => {
  const contextValue = useContext(AppContext);

  const [watchClicked, setWatchClicked] = useState(false);
  const [isLiked, setIsLiked] = useState(false);
  const [key, setKey] = useState('');
  const [playTrailer, setPlayTrailer] = useState(false);
  const [noTrailer, setNoTrailer] = useState(false);

  useEffect(() => {
    handleFavoritesList();
  }, []);

  console.log(handleNewLikes);

  const handleTrailerClick = () => {
    axios
      .get(`https://api.themoviedb.org/3/movie/${item.id}?api_key=${process.env.MOVIEDB_API_KEY}&append_to_response=videos`)
      .then(response => {
        const trailer = response.data.videos.results.find(vid => vid.name === 'Official Trailer');
        if (response.data.videos.results.length === 0) {
          setKey('');
          setPlayTrailer(false);
          setNoTrailer(true);
          document.body.style.overflowY = 'hidden';
        } else if (trailer) {
          setKey(trailer);
          setPlayTrailer(true);
        } else if (!trailer) {
          setKey(response.data.videos.results[0]);
          setPlayTrailer(true);
        }
      });
    setWatchClicked(true);
    document.body.style.overflowY = 'hidden';
  };

  const handleLikes = () => {
    const token = window.localStorage.getItem('trailerflix-jwt');
    if (token && contextValue?.user?.user) {
      axios.post('/auth/likes', item, {
        headers: {
          'Content-Type': 'application/json',
          'X-Access-Token': token
        }
      })
        .then(response => {
          console.log(handleNewLikes);
          handleNewLikes(item);
          setIsLiked(true);
        })
        .catch(error => {
          console.error('Fetch failed during POST', error);
        });
    } else {
      window.alert('You need to be signed in to save a movie!');
    }
  };

  const handleFavoritesList = () => {
    const token = window.localStorage.getItem('trailerflix-jwt');
    if (token && contextValue.user?.user) {
      axios.get('/auth/get-likes', {
        headers: {
          'Content-Type': 'application/json',
          'X-Access-Token': token
        }
      })
        .then(response => {
          const result = response.data;
          const isItemLiked = result.some(obj => obj.favoritedItem.id === item.id);
          setIsLiked(isItemLiked);
        })
        .catch(error => {
          console.error('Fetch failed during GET', error);
        });
    } else {
      setIsLiked(false);
    }
  };

  const truncateString = (str, num) => {
    if (str?.length > num) {
      return str.slice(0, num) + '...';
    } else {
      return str;
    }
  };

  const { title, original_title, media_type, name } = item;

  return (
    <>
      {watchClicked && (
        <Player trailer={key} playTrailer={playTrailer} noTrailer={noTrailer} onClose={() => setWatchClicked(false)} />
      )}
      <div className="w-[200px] sm:w-[300px] lg:w-[400px] inline-block cursor-pointer relative transition duration-200 ease-out p-2 lg:mr-1 sm:mr-2 md:hover:scale-105">
        <img
          className="w-full h-auto block rounded-sm object-cover md:rounded"
          src={`https://image.tmdb.org/t/p/w500/${rowId === '1' || rowId === '4' ? item?.poster_path : item?.backdrop_path}`}
          alt={title || original_title || name || media_type || 'Title Unavailable'}
        />
        <div className="absolute top-0 left-0 w-full h-full hover:bg-black/80 opacity-0 hover:opacity-100 ease-in duration-300 text-white">
          <div className="white-space-normal text-xs md:text-sm lg:text-base font-bold flex justify-center items-center text-center h-full">
            <div className="flex-wrap">
              <p className="mb-2">
                {truncateString(title || original_title || name || media_type || 'Title Unavailable', 35)}
              </p>
              <div>
                <a
                  className="border bg-gray-300 text-black border-gray-300 py-1 px-1 text-xs lg:text-base hover:bg-red-600 hover:border-red-600 hover:text-gray-300 ease-in duration-250"
                  onClick={handleTrailerClick}
                >
                  Watch
                </a>
              </div>
            </div>
          </div>
          <p onClick={() => handleLikes()}>
            {isLiked && contextValue.user?.user
              ? (
                <FaHeart className="absolute top-4 left-4 text-red-600" />
                )
              : (
                <FaRegHeart className="absolute top-4 left-4 hover:text-red-600 ease-in duration-100" />
                )}
          </p>
        </div>
      </div>
    </>
  );
};

export default Media;

在媒體中,該函數有時會在控制台中定義,有時會未定義。另外,當我單擊心形圖示並觸發handleLikes函數時,其中的handleNewLikes函數將始終顯示為未定義。

這就是我在控制台中看到的

謝謝你的幫助,我被困了幾天了lmao

我知道我可能可以使用某種狀態管理庫,但這需要相當多的重構,我覺得這應該可行,我不明白為什麼會出現任何問題,除非我目光短淺。

P粉818125805P粉818125805258 天前412

全部回覆(1)我來回復

  • P粉762730205

    P粉7627302052024-02-27 09:48:52

    我複製了你的程式碼,並添加了一些日誌來幫助你看到發生了什麼。你只從Home -> MainHome -> Row 0傳遞了handleNewLikes

    其餘的行的handleNewLikes正確地為undefined,因為你沒有傳遞這個函數。查看這些日誌以了解我的意思:

    當我們到達Media元件時,你在呼叫handleLikes時嘗試使用一個undefinedhandleNewLikes函數。

    // Media.tsx line 48
      const handleLikes = () => {
        const token = window.localStorage.getItem("trailerflix-jwt");
        if (token && contextValue?.user?.user) {
          axios
            .post("/auth/likes", item, {
              headers: {
                "Content-Type": "application/json",
                "X-Access-Token": token
              }
            })
            .then((response) => {
              console.log(handleNewLikes);
              // Undefined because not passed in to Row which passes to Media
              handleNewLikes(item);
              setIsLiked(true);
            })
            .catch((error) => {
              console.error("Fetch failed during POST", error);
            });
        } else {
          window.alert("You need to be signed in to save a movie!");
        }
      };

    這是在Media元件中點擊like按鈕時的一些日誌:

    最簡單的解決方案是在Home中更新Row實現的參數,包括handleNewLikes

    <Row
      rowId='1'
      title='Top 10 Movies in the U.S. Today'
      fetchURL={requests.popular}
      handleNewLikes={handleNewLikes}
    />
    // 同樣適用於row 2、3等...

    這是在將函數傳遞給Row 1後點擊like按鈕的日誌

    ^ 你應該使用某種狀態管理。你不需要使用一個函式庫來做這個。如果狀態不是非常複雜,你可以使用類似於你在AuthContext中所做的React上下文。

    回覆
    0
  • 取消回覆