при обновлении state react не перерисовывает приложение
Всем привет! Недавно начал изучать React и столкнулся со следующей проблемой. При отправке формы изменяю state (через переданную функцию updateMovies). State меняется (проверяю это через console.log в render() в компоненте App), но сама страница не перерисовывается. Хотя если что-то изменить в коде и сохранить, изменения отобразятся. Проект на GitHub
(Api с помощью которого получаю информацию о фильмах без впн не работает!!!)
App
import React, { Component } from 'react';
import MovieList from '../movie-list/movie-list';
import getListMovie from '../../server-api/get-list-movie';
import { Offline, Online } from 'react-detect-offline';
import './app.css';
import Search from '../search/search';
import { Pagination } from 'antd';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
movies: [],
};
}
componentDidMount() {
getListMovie()
.then((movies) => {
this.setState({ movies: movies.results });
})
.catch((err) => {
console.error('Error:', err);
});
}
// Метод для обновления списка фильмов
updateMovies = (movies) => {
this.setState({ movies });
};
addNewMovie = (movie) => {
this.setState((prevState) => ({
movies: [...prevState.movies, movie],
}));
};
render() {
console.log('Rendered movies:', this.state.movies);
const { movies } = this.state;
return (
<div className="app">
<Online>
<Search updateMovies={this.updateMovies} />
<MovieList movies={movies} addNewMovie={this.addNewMovie} />
</Online>
<Offline>
<span>Sorry not connection</span>
</Offline>
</div>
);
}
}
Search
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './search.css';
import filmSearch from '../../server-api/film-search';
export default class Search extends Component {
constructor(props) {
super(props);
this.state = {
label: '',
};
}
onSubmit = (e) => {
e.preventDefault();
const { label } = this.state;
const { updateMovies } = this.props;
if (label.trim()) {
filmSearch(label)
.then((searchInfo) => {
updateMovies(searchInfo.results);
})
.catch((error) => {
console.error('Error searching for movies:', error);
});
this.setState({
label: '',
});
}
};
onLabelChange = (e) => {
const { value } = e.target;
this.setState({
label: value,
});
};
render() {
const { label } = this.state;
return (
<form onSubmit={this.onSubmit} className="search">
<input
className="search-input"
placeholder="Type to search..."
onChange={this.onLabelChange}
value={label}
/>
</form>
);
}
}
Search.propTypes = {
updateMovies: PropTypes.func.isRequired,
};
MovieList
import React from 'react';
import Movie from '../movie/movie';
import PropTypes from 'prop-types';
import './movie-list.css';
function MovieList({ movies, addNewMovie }) {
const elements = movies.map((item) => {
const { id } = item;
return <Movie id={id} addNewMovie={addNewMovie} />;
});
return <ul className="movie-list">{elements}</ul>;
}
MovieList.propTypes = {
movies: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
})
).isRequired,
addNewMovie: PropTypes.func.isRequired,
};
export default MovieList;
Movie
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './movie.css';
import { Spin, Alert } from 'antd';
import getMovieInfo from '../../server-api/get-movie-info';
export default class Movie extends Component {
state = {
loading: true,
hasError: false,
errorMessage: '',
movieInfo: null,
};
componentDidMount() {
const { id, addNewMovie } = this.props;
getMovieInfo(id)
.then((movieInfo) => {
addNewMovie(id, movieInfo);
this.setState({ movieInfo, loading: false });
})
.catch((error) => {
this.setState({
hasError: true,
errorMessage: error.message || 'Failed to fetch movie information.',
loading: false,
});
});
}
truncateText = (text, maxLength) => {
if (!text) return '';
if (text.length <= maxLength) return text;
let truncated = text.slice(0, maxLength);
if (text[maxLength] !== ' ' && truncated[truncated.length - 1] !== ' ') {
truncated = truncated.slice(0, truncated.lastIndexOf(' '));
}
return `${truncated}...`;
};
render() {
const { loading, hasError, errorMessage, movieInfo } = this.state;
if (hasError) {
return (
<div className="movie-error">
<Alert
message="Error"
description={errorMessage}
type="error"
showIcon
/>
</div>
);
}
if (loading) {
return (
<div className="movie-spinner">
<Spin size="large" tip="Loading movie details..." />
</div>
);
}
if (!movieInfo) {
return <p className="movie-not-found">Movie information not found.</p>;
}
return (
<div className="movie">
<MovieInfo movieInfo={movieInfo} truncateText={this.truncateText} />
</div>
);
}
}
Movie.propTypes = {
id: PropTypes.number.isRequired,
movies: PropTypes.object.isRequired,
addNewMovie: PropTypes.func.isRequired,
};
const MovieInfo = ({ movieInfo, truncateText }) => {
const { title, release_date, poster_path, overview, genres } = movieInfo;
return (
<React.Fragment>
<img
className="movie-image"
src={`https://image.tmdb.org/t/p/w500${poster_path}`}
alt={`${title} poster`}
/>
<div className="container">
<h5 className="movie-title">{title}</h5>
<span className="movie-date">{release_date}</span>
{genres && genres.length > 0 && (
<div className="movie-categories">
{genres.map((genre) => (
<div key={genre.id} className="movie-genre">
{genre.name}
</div>
))}
</div>
)}
<div className="movie-description">
{truncateText(overview || 'No description available', 200)}
</div>
</div>
</React.Fragment>
);
};
MovieInfo.propTypes = {
movieInfo: PropTypes.shape({
title: PropTypes.string.isRequired,
release_date: PropTypes.string.isRequired,
poster_path: PropTypes.string,
description: PropTypes.string.isRequired,
genres: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
})
),
}).isRequired,
truncateText: PropTypes.func.isRequired,
};
Ответы (2 шт):
При отправке формы изменяю state (через переданную функцию updateMovies). State меняется (проверяю это через console.log в render() в компоненте App), но сама страница не перерисовывается.
Есть мнение... (с) Что происходит потеря контекста.
Вот такой способ передачи метода <Search updateMovies={this.updateMovies} />
возможен, если метод "забиндить" на нужный контекст
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
movies: [],
};
this.updateMovies = this.updateMovies.bind(this)
}
}
Или вызывать метод в правильном контексте
<Search updateMovies={m => this.updateMovies(m)} />
При формировании списка в map обязательно указывать key, иначе render работает не корректно
function MovieList({ movies, addNewMovie }) {
return (
<ul className="movie-list">
{movies.map((item) => {
return (
<Movie
id={item.id}
key={item?.id?.toString()}
addNewMovie={addNewMovie}
/>
);
})}
</ul>
);
}