【问题标题】:React Router props.match and props.history.push are undefined while using useContextReact Router props.match 和 props.history.push 在使用 useContext 时未定义
【发布时间】:2020-03-03 08:22:25
【问题描述】:

我已将我的项目移至 Codesandbox,以便更好地解决我的问题。这是link to the project.

我有两个组件 SearchFormAnimeDetails 接收来自我的上下文组件 AnimeContext 的 API 调用。该表单旨在显示所请求的搜索动漫,AnimeDetails 应该显示所选动漫的详细信息。

我正在使用props.match.params 获取TopAnime 组件中动画的ID,使用<Link to={} />props.history.push 在提交表单后重定向到新页面。

当我尝试点击动漫卡片以获取详细信息时,我收到了

props.match 未定义

当我提交表单时,我看到搜索到的动漫出现了,但随后我收到了

props.history 未定义

我假设这是 React Router 问题,并且我没有正确设置某些东西。

这是我到目前为止所做的尝试,但没有任何效果:

  • 使用Redirect

  • 使用useHistory 挂钩

  • withRouter包装AnimeProvider

简而言之,如果props.matchprops.history 未定义,我无法搜索任何标题,也无法单击主页上的任何动漫标题以获取其详细信息。

SearchForm 组件

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 组件

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 组件

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 }

【问题讨论】:

  • 这能回答你的问题吗? How to push to History in React Router v4?
  • 它帮助我更好地理解了问题的一部分,但我仍然对props.match 有疑问,因为它仍未定义。我将它从我的上下文组件中删除,并直接在我的详细信息组件中使用它以使其现在可以正常工作。谢谢。

标签: reactjs react-router react-hooks react-router-v4 react-router-dom


【解决方案1】:

你应该这样做

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

【讨论】:

  • 您好,欢迎来到 Stack Overflow!请拨打tour。您能否添加关于您的代码如何解决问题的说明?
猜你喜欢
  • 2018-07-25
  • 1970-01-01
  • 2021-09-20
  • 1970-01-01
  • 2020-12-08
  • 2020-03-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多