i'am learning TS yet and I trying to create an application where I get data from API, show results and if someone click on item, it shows a modal with more details, but i'am trouble cause basically my component doesn't render... Look at my code =) !
import IMovie from "../../models/movie.model";
import Modal from "../modal/Modal";
import "./style";
import {
ResultsBody,
ResultsContainer,
TitleResult,
MovieStats,
MovieCover,
MovieStatsDescription,
} from "./style";
interface ISearch {
search?: string;
}
const URL =
"#";
const Results = ({ search }: ISearch) => {
const [data, setData] = React.useState<IMovie[]>([]);
const [currentPage, setCurrentPage] = React.useState(1);
const [dataPerPage] = React.useState(10);
async function getData() {
const response: AxiosResponse<any> = await axios.get(URL);
setData(response.data.results);
}
React.useEffect(() => {
getData();
}, []);
const indexLastData = currentPage * dataPerPage;
const indexFirstData = indexLastData - dataPerPage;
const currentData = data.slice(indexFirstData, indexLastData);
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
const filteredData = data.filter((results) => {
return results.title.toLowerCase().includes(search!.toLocaleLowerCase());
});
return (
<>
<ResultsContainer>
<TitleResult>
<span>Personagem</span>
<span>Sinopse</span>
<span>Data</span>
</TitleResult>
{!search
? currentData.map((item) => (
<ResultsBody
key={item.id}
// onClick={() => {
// selectedMovie(item);
// }}
>
<MovieCover
src={`https://image.tmdb.org/t/p/w185${item.poster_path}`}
alt="poster"
/>
<MovieStats style={{ fontWeight: `bold` }}>
{item.title}
</MovieStats>
<MovieStatsDescription>{item.overview}</MovieStatsDescription>
<MovieStats>{item.release_date}</MovieStats>
</ResultsBody>
))
: filteredData.map((item) => (
<ResultsBody key={item.id}>
<MovieCover
src={`https://image.tmdb.org/t/p/w185${item.poster_path}`}
alt="poster"
/>
<MovieStats style={{ fontWeight: `bold` }}>
{item.title}
</MovieStats>
<MovieStatsDescription>{item.overview}</MovieStatsDescription>
<MovieStats>{item.release_date}</MovieStats>
</ResultsBody>
))}
</ResultsContainer>
<Modal data={data} /> //HERE IS WHERE I'AM CALLING MY MODAL, I want to pass data here
<Pagination
dataPerPage={dataPerPage}
totalData={data.length}
paginate={paginate}
currentPage={currentPage}
/>
</>
);
};
export default Results;
This is my MODAL component
import React from "react";
import { ModalContainer } from "./style";
import IMovie from "../../models/movie.model";
interface IData {
data: IMovie[];
}
const Modal = ({ data }: IData) => {
console.log(data);
return <ModalContainer>{data.title}</ModalContainer>; //HERE IS NOT WORKING
};
export default Modal;
As you can see guys, I can show all results on console.log, but when I put inside the return the log says ''TypeError: Cannot read property 'title' of undefined''
If someone could help me I'd really appreciate! Thanks a lot =)
You are getting the error
'Property 'title' does not exist on type 'IMovie[]'. TS2339
in your Modal
component because data
is an array
of movies. An array doesn't have a title
property.
You want the modal to show one movie, so you should only pass it one movie.
interface IData {
data: IMovie;
}
Changing the IData
interface fixes the issues in Modal
, but creates a new error in Results
because we are still passing an array. The correct prop is the data for the movie that was clicked. What movie is that? We need to use a useState
hook in order to store that data.
Depending on where you control the open/closed state of the Modal
, you may also want to pass an onClose
callback that clears the selected movie state.
the state:
const [selected, setSelected] = React.useState<IMovie | null>(null); // is a movie or null
in the movie:
onClick={() => setSelected(item)}
the modal:
{selected === null || (
<Modal
data={selected}
onClose={() => setSelected(null)}
/>
)}
You are rendering a movie the same way whether it's from currentData
or filteredData
, so we want to combine those. We could create a shared renderMovie
callback or ResultsMovie
component to use in both loops, but I think we can actually handle it higher up and just have one loop.
You also want your pagination to reflect the pages of just the matching movies when we are filtering based on a search.
// the matchingMovies is a filtered array when there is a search, or otherwise the entire array
const matchingMovies = search
? data.filter((result) =>
result.title.toLowerCase().includes(search.toLowerCase())
)
: data;
const indexLastData = currentPage * dataPerPage;
const indexFirstData = indexLastData - dataPerPage;
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
// total for the pagination should be based on matchingMovies instead of data
const totalData = matchingMovies.length;
// make the currentData from the matchingMovies
const currentData = matchingMovies.slice(indexFirstData, indexLastData);
There might be some bugs or potential additional improvements but I can't actually run this without your components:)
const Results = ({ search }: ISearch) => {
const [data, setData] = React.useState<IMovie[]>([]);
const [currentPage, setCurrentPage] = React.useState(1);
const [dataPerPage] = React.useState(10);
const [selected, setSelected] = React.useState<IMovie | null>(null); // is a movie or null
async function getData() {
const response: AxiosResponse<any> = await axios.get(URL);
setData(response.data.results);
}
React.useEffect(() => {
getData();
}, []);
// the matchingMovies is a filtered array when there is a search, or otherwise the entire array
const matchingMovies = search
? data.filter((result) =>
result.title.toLowerCase().includes(search.toLowerCase())
)
: data;
const indexLastData = currentPage * dataPerPage;
const indexFirstData = indexLastData - dataPerPage;
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
// make the currentData from the matchingMovies
const currentData = matchingMovies.slice(indexFirstData, indexLastData);
return (
<>
<ResultsContainer>
<TitleResult>
<span>Personagem</span>
<span>Sinopse</span>
<span>Data</span>
</TitleResult>
{currentData.map((item) => (
<ResultsBody key={item.id} onClick={() => setSelected(item)}>
<MovieCover
src={`https://image.tmdb.org/t/p/w185${item.poster_path}`}
alt="poster"
/>
<MovieStats style={{ fontWeight: `bold` }}>{item.title}</MovieStats>
<MovieStatsDescription>{item.overview}</MovieStatsDescription>
<MovieStats>{item.release_date}</MovieStats>
</ResultsBody>
))}
</ResultsContainer>
{selected === null || (
<Modal data={selected} onClose={() => setSelected(null)} />
)}
<Pagination
dataPerPage={dataPerPage}
totalData={matchingMovies.length}
paginate={paginate}
currentPage={currentPage}
/>
</>
);
};
interface ModalProps {
data: IMovie;
onClose: () => void;
}
const Modal = ({ data, onClose }: ModalProps) => {
console.log(data);
return <ModalContainer>{data.title}</ModalContainer>;
};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.