繁体   English   中英

React 功能组件不会重新渲染

[英]React functional component does not re-render

我正在使用 React 构建电影卡片列表。

在卡片 hover 上,显示了一个收藏电影的图标。 单击该图标后,电影将被收藏,该图标将被另一个图标替换,表明该电影已被收藏:

在此处输入图像描述

相对

在此处输入图像描述

问题在于,为了实现这一点,必须重新加载浏览器(尽管 DevTools 中的 React Profiler 显示卡片在其图标被点击后重新呈现)。 如何修复它以便在单击后立即替换图标?

<Card />组件:

import React from 'react';
import { selectMovie, isMoviePageOpened } from '../../../main.actions';
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder';
import { Grid, IconButton } from '@material-ui/core';
import noImage from '../../../images/no-image-available.png';
import { NavLink, useLocation } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import useStyles from './Card.styles';
import { RootState } from '../../../store';
import { CardProps } from './Card.types';
import FavoriteIcon from '@material-ui/icons/Favorite';
import { getUserName, isUserLoggedIn as isCurrentUserLoggedIn } from '../../../utils/common.utils';
import { favouriteMovie, removeFavouriteMovie } from '../../../utils/movies.utils';
const posterBaseUrl = 'https://image.tmdb.org/t/p/w300';

const Card: React.FC<CardProps> = ({ card }: CardProps) => {
    const dispatch = useDispatch();
    const classes = useStyles();
    const { pathname } = useLocation();

    const favouriteMovies = useSelector((state: RootState) => state.profile.favouriteMovies);

    const isProfilePageOpened = String(pathname.split('/').pop()) === 'profile' ? true : false;
    const isUserLoggedIn = isCurrentUserLoggedIn();
    const isMovieFaved = favouriteMovies.includes(card.id);
    const currentUser = getUserName();

    const changeIsMovieFavourite = () => {
        isMovieFaved ? removeFavouriteMovie(currentUser, card.id) : favouriteMovie(currentUser, card.id);
    };

    const SetSelectedMovieId = (movieId: number) => {
        dispatch(isMoviePageOpened(true));
        dispatch(selectMovie(movieId));
    };
    console.log(card.id, 'isMovieFaved ', isMovieFaved);
    return (
        <Grid item key={card.id}>
            <div className="card-container" onClick={() => SetSelectedMovieId(card.id)}>
                <NavLink to={'/movie/' + card.id} data-testid="catalog-card">
                    <img
                        className={isProfilePageOpened ? classes.profileCard : classes.primaryCard}
                        alt={'Poster of ' + card.title}
                        src={
                            card.poster_path
                                ? card.poster_path.includes('.jpg')
                                    ? posterBaseUrl + card.poster_path
                                    : noImage
                                : noImage
                        }
                        title={card.title}
                    />
                </NavLink>
                <div className="card-details">
                    <IconButton className="fav-icon" onClick={changeIsMovieFavourite}>
                        <NavLink to={isUserLoggedIn ? '' : '/login'} className="fav-icon-button">
                            {isMovieFaved ? <FavoriteIcon /> : <FavoriteBorderIcon />}
                        </NavLink>
                    </IconButton>
                    <div className="vote-average">{card.vote_average}</div>
                </div>
            </div>
        </Grid>
    );
};

export default Card;

<CatalogCards />组件遍历<Card />

import React from 'react';
import { useRef, useEffect } from 'react';
import { Grid, CardMedia } from '@material-ui/core';
import '../../../App.scss';
import loadingSpinner from '../../../images/loading-spinner.gif';
import useIntersectionObserver from '../../../customHooks/useIntersectionObserver';
import { changePageSrolledTill } from '../../../main.actions';
import { fetchAllMovies, Movie } from '../../../services/movies.services';
import { showMoviesAtHomePage } from '../../../main.actions';
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
import Card from '../../GeneralComponents/Card/Card';
import useStyles from './CatalogCards.styles';
import { RootState } from '../../../store';
import { textConstants } from '../../../constants';

const CatalogCards: React.FC = () => {
    const loadingRef = useRef<HTMLDivElement | null>(null);
    const entry = useIntersectionObserver(loadingRef, {});
    const isVisible = !!entry?.isIntersecting;
    const dispatch = useDispatch();
    const movies = useSelector((state: RootState) => state.movies.homePageMovies);
    const searchedMovie = useSelector((state: RootState) => state.movies.searchedMovie);
    const pageSrolledTill = useSelector((state: RootState) => state.movies.pageSrolledTill);
    const classes = useStyles();

    useEffect(() => {
        if (isVisible) {
            if (pageSrolledTill <= 500) {
                dispatch(changePageSrolledTill(pageSrolledTill + 1));

                fetchAllMovies(String(pageSrolledTill))
                    .then(nextPage => {
                        dispatch(showMoviesAtHomePage([...movies, ...nextPage]));
                    })
                    .catch(() => {
                        dispatch(showMoviesAtHomePage([...movies]));
                    });
            }
        }
    }, [isVisible]);

    return (
        <div>
            {movies.length > 0 && movies.length < 6 && (
                <div className={classes.searchResultsTitle}>{textConstants.MOVIES_FOUND}</div>
            )}
            {movies.length > 0 ? (
                <Grid container className={classes.container}>
                    {movies
                        .filter((movie: Movie) => movie.vote_average !== 0)
                        .map((movie: Movie) => (
                            <Card key={movie.id} card={movie} />
                        ))}
                </Grid>
            ) : searchedMovie ? (
                <div className={classes.noResultsMessage}>{textConstants.TRY_DIFFERENT_PHRASE}</div>
            ) : (
                <CardMedia component="img" image={loadingSpinner} className={classes.loadingSpinner} />
            )}
            {!searchedMovie && (
                <div ref={loadingRef}>{pageSrolledTill <= 500 ? '' : textConstants.ALL_MOVIES_SEEN}</div>
            )}
        </div>
    );
};

export default CatalogCards;

最喜欢的电影()function:

export const favouriteMovie = async (currentUser: string, movieId: number) => {
    const userData = {
        userName: currentUser,
        movie: movieId,
    };

    fetch('http://localhost:8082/favouritemovie/', {
        method: 'POST',
        body: JSON.stringify(userData),
        headers: {
            'Content-type': 'application/json; charset=UTF-8',
        },
    }).then(async response => {
        const res = await response.json();

        if (response.status === 200) {
            return;
        } else {
            return res.errorMsg;
        }
    });
};

removeFavouriteMovie() function:

export const removeFavouriteMovie = async (currentUser: string, movieId: number) => {
    const userData = {
        userName: currentUser,
        movie: movieId,
    };

    fetch('http://localhost:8082/removefavouritemovie/', {
        method: 'POST',
        body: JSON.stringify(userData),
        headers: {
            'Content-type': 'application/json; charset=UTF-8',
        },
    }).then(async response => {
        const res = await response.json();

        if (response.status === 200) {
            return;
        } else {
            return res.errorMsg;
        }
    });
};

谢谢!

您的 API 呼叫应该在Redux Thunk中,然后调度到商店更新电影设置列表,无论它是否被收藏。

export const removeFavouriteMovie = (request) => {
    return async (dispatch) => {
        const response = await fetch('http://localhost:8082/favouritemovie/', {
        method: 'POST',
        body: JSON.stringify(request),
        headers: {
            'Content-type': 'application/json; charset=UTF-8',
        },
      });
      if (response.status === 200) {
        // New reducer to unfavorite the movie
        dispatch(unfavoriteMovie(request.movie));
      } else {
        // New reduce to notify the user of the error
        const res = await response.json();
        dispatch(setErrorMessage(`There was a problem saving the movie: ${res.errorMsg}`);
      }
    };
}

然后这个 function 应该更新为使用 dispatch 来调用 thunk。

const changeIsMovieFavourite = () => {
  isMovieFaved ?
  dispatch(removeFavouriteMovie({ userName: currentUser, movie: card.id }) :
  dispatch(favouriteMovie({userName: currentUser, movie: card.id});
};

只知道如果 API 调用需要一段时间,用户可能认为他们没有取消对电影的喜爱,因此您可能想要切换组件中的心形填充,然后如果它成功,心形填充将保持为空,但如果显示错误up 用户收到错误消息,心脏填充恢复为固体。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM