[英]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.