简体   繁体   中英

Pass data from API to another component with TypeScript and ReactJS

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 =)

Movie vs Array

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;
}

Current Selection

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)}
  />
)}

Avoid Duplicated Code Blocks

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.

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