0

I have moved my project to Codesandbox for better assistance with my question. Here is the link to the project.

I have two components SearchForm and AnimeDetails that receive API calls from my context component AnimeContext. The form is meant to display the searched Anime that was requested and AnimeDetails is supposed to display the details for the selected Anime.

I'm using props.match.params to get the id of the anime within the TopAnime component using <Link to={} /> and props.history.push to redirect to a new page once the form is submitted.

When I attempt to click on an Anime card to get the details, I receive

props.match is undefined

When I submit the form, I see the searched anime appear, but then I receive

props.history is undefined

I'm assuming this is React Router issue and that I am not setting something up correctly.

Here's what I have attempted so far and nothing has worked:

  • Using Redirect

  • Using the useHistory hook

  • Wrapping AnimeProvider with withRouter

In short, I cannot search for any titles and I cannot click on any Anime title on the HomePage to get it's details without getting undefined for props.match and props.history.

SearchForm component

import React, { useContext } from 'react';
import { withRouter } from 'react-router-dom'
import styled from 'styled-components'
import AnimeCard from './AnimeCard/AnimeCard';
import { AnimeContext } from '../store/AnimeContext'


const SearchForm = () => {
  const { dataItems, animeSearched, handleSubmit } = useContext(AnimeContext)

  return (
    <div>
      <Form onSubmit={handleSubmit}>
        <Input
          type="text"
          name="anime"
          placeholder="Enter title"
        // ref={value => myValue = value}
        />
        <FormButton type='submit'>Search</FormButton>
      </ Form>
      {animeSearched
        ?
        <AnimeCard />
        : null}
    </div>
  )
}

export default withRouter(SearchForm)

AnimeDetails component

import React, { useContext, useEffect } from "react";
import styled from "styled-components";

import { AnimeContext } from "../store/AnimeContext";

const AnimeDetails = () => {
  const { fetching, anime, fetchAnimeDetails } = useContext(AnimeContext);

  useEffect(() => {
    fetchAnimeDetails();
  });

  return (
    <>
      {fetching && "Fetching..."}
      {anime && (
        <AnimeDetailsWrapper>
          <AnimeDetailsContainer>
            <Poster src={anime.image_url} />
            {/* Details */}
            <Details>
              <Title>{anime.title}</Title>
              <TitleJpn>{anime.title_japanese}</TitleJpn>
              <Score>{anime.score || "N/A"}</Score>
              {/* If no score then display N/A */}
              <SongList>
                <h3>Opening Themes</h3>
                {anime.opening_themes // Make sure data is fully loaded before component renders
                  ? anime.opening_themes.map((song, index) => (
                      <li key={index}>{song}</li>
                    ))
                  : null}
              </SongList>
            </Details>
            {/* Info Bar */}
            <InfoBar>
              {
                <li>
                  Epiosdes: <span className="info-span">{anime.episodes}</span>
                </li>
              }
              {
                <li>
                  Duration: <span className="info-span">{anime.duration}</span>
                </li>
              }
              {
                <li>
                  <a
                    href={anime.trailer_url}
                    rel="external noopener noreferrer"
                    target="_blank"
                  >
                    View Trailer
                  </a>
                </li>
              }
            </InfoBar>
            {/* Synopsis */}
            <Synopsis>{anime.synopsis}</Synopsis>
          </AnimeDetailsContainer>
        </AnimeDetailsWrapper>
      )}
    </>
  );
};

export default AnimeDetails;

AnimeContext component

import React, { useState, useEffect, createContext } from 'react'

const AnimeContext = createContext()

const API = "https://api.jikan.moe/v3"


const AnimeProvider = (props) => {
  const urls = [
    `${API}/top/anime/1/airing`,
    `${API}/top/anime/1/tv`,
    `${API}/top/anime/1/upcoming`,
  ]

  // State for top Anime 
  const [topTv, setTopTv] = useState([])
  const [topAiring, setTopAiring] = useState([])
  const [topUpcoming, setTopUpcoming] = useState([])

  // State for Anime details
  const [animeReq, setAnimeReq] = useState({
    fetching: false,
    anime: []
  })

  // State for Anime search form
  const [dataItems, setDataItems] = useState([])
  const [animeSearched, setAnimeSearched] = useState(false)


  // Fetch top Anime 
  const fetchTopAnime = async () => {
    return Promise.all(
      urls.map(async url => {
        return await fetch(url); // fetch data from urls
      })
    )
      .then((responses) => Promise.all(responses.map(resp => resp.json())) // turn data into JSON
        .then(data => {
          const topTvFiltered = data[0].top.filter(item => item.rank <= 5) // filter out top 6 
          const topAiringFiltered = data[1].top.filter(item => item.rank <= 5)
          const topUpcomingFiltered = data[2].top.filter(item => item.rank <= 5)

          setTopTv(topTvFiltered)
          setTopAiring(topAiringFiltered)
          setTopUpcoming(topUpcomingFiltered)

          console.log(data)
        })
      )
      .catch(err => console.log("There was an error:" + err))
  }

  useEffect(() => {
    fetchTopAnime()
  }, [])

  // Fetch Anime details
  const fetchAnimeDetails = async () => {
    setAnimeReq({ fetching: true })

    const response = await fetch(`${API}/${props.match.params.animeId}`)
    const data = await response.json()

    console.log(data);
    setAnimeReq({ fetching: false, anime: data }) // set initial state to hold data from our API call
  }

  const { fetching, anime } = animeReq;

  // Fetch searched Anime
  async function handleSubmit(e) {
    e.preventDefault()

    const animeQuery = e.target.elements.anime.value
    const response = await fetch(`${API}/search/anime?q=${animeQuery}&page=1`)
    const animeData = await response.json()

    setDataItems(animeData.results)
    setAnimeSearched(!animeSearched)

    props.history.push('/dashboard')

  }


  return (
    <AnimeContext.Provider value={{
      topTv,
      setTopTv,
      topAiring,
      setTopAiring,
      topUpcoming,
      setTopUpcoming,
      dataItems,
      setDataItems,
      animeSearched,
      setAnimeSearched,
      fetching,
      anime,
      fetchTopAnime,
      fetchAnimeDetails,
      handleSubmit
    }}>
      {props.children}
    </AnimeContext.Provider>
  )
}

export { AnimeProvider, AnimeContext }
Dave.Q
  • 369
  • 1
  • 6
  • 21
  • Does this answer your question? [How to push to History in React Router v4?](https://stackoverflow.com/questions/42701129/how-to-push-to-history-in-react-router-v4) – akhtarvahid Nov 07 '19 at 03:14
  • It helped me better understand one part of the issue, but I'm still having an issue with ```props.match``` as it's still undefined. I removed it from my context component and I'm using it in my details component directly to get it working for now. Thank you. – Dave.Q Nov 08 '19 at 15:47

1 Answers1

0

You should do this

import { useLocation, useNavigate } from 'react-router-dom';

//You should navigate
  const navigate = useNavigate();
  navigate('/app/example', { state: { message: "hello" } });

//You can receive
 const location = useLocation();
    console.log("location data", location.state);
hsnozacar
  • 11
  • 1
  • 1
    Hi and welcome to Stack Overflow! Please take the [tour](https://stackoverflow.com/tour). Can you add an explanation on how your code solves the problem? – Jeanne Dark Nov 29 '20 at 10:28