简体   繁体   中英

React Context doesn't render component

I'm using context and React Router. When I created my context and wrapped my components, nothing rendered. When I check the console, I can see the logged data that I'm fetching (it's coming from useEffect in AnimeContext ) but the Header and HomePage components don't appear.

I'm attempting to display topTv , topAiring , and topUpcoming on the HomePage .

Here's the repo

Context file

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
  }


  // Fetch searched Anime
  const handleSubmit = async (e) => {
    e.preventDefault()

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

    const animeData = await response.json()
    // const topAnime = await response2.json()

    setDataItems(animeData.results)
    setAnimeSearched(!animeSearched)

    props.history.push('dashboard')
  }

  const { fetching, anime } = animeReq;

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

export { AnimeProvider, AnimeContext }

App.js

import React, { Component } from 'react';

import styled, { ThemeProvider } from 'styled-components';
import theme from './config/theme';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import { AnimeProvider } from './store/AnimeContext'

import Header from './Components/Header';
import HomePage from './Components/Home Page/HomePage';
import AnimeDetails from './Components/AnimeDetails';
import AnimeCard from './Components/AnimeCard/AnimeCard'


class App extends Component {
  render() {
    return (
      <AnimeProvider>
        <Router>
          <ThemeProvider theme={theme}>
            <AppWrapper>
              <Header />
              <Switch>
                <Route path='/' exact component={HomePage} />
                <Route path='/dashboard' exact component={AnimeCard} />
                <Route path='/:animeId' component={AnimeDetails} />
              </Switch>
            </AppWrapper>
          </ThemeProvider>
        </Router>
      </AnimeProvider>
    );
  }
}

const AppWrapper = styled.div`
  text-align: center;
  font-size: calc(10px + 1vmin);
  color: white;
        `

export default App;

HomePage Component

import React, { useContext } from 'react'
import styled from 'styled-components'

import { TopAnime } from './TopAnime';
import { AnimeContext } from '../../store/AnimeContext'

const HomePage = () => {
  const [topTv, topAiring, topUpcoming,] = useContext(AnimeContext)

  return (
    <AnimeContext.Consumer>
      <HomeWrapper>
        <TopAni>
          {topTv.length > 0 ? <TopAniTitle>Top TV</TopAniTitle> : null}
          {topTv.map((item, index) => (
            <TopAnime
              key={index}
              image={item.image_url}
              title={item.title}
              item={item}
            />
          ))}
        </TopAni>

        <TopAni>
          {topAiring.length > 0 ? <TopAniTitle>Top Airing</TopAniTitle> : null}
          {topAiring.map((item, index) => (
            <TopAnime
              key={index}
              image={item.image_url}
              title={item.title}
              item={item}
            />
          ))}
        </TopAni>

        <TopAni>
          {topUpcoming.length > 0 ? <TopAniTitle>Top Upcoming</TopAniTitle> : null}
          {topUpcoming.map((item, index) => (
            <TopAnime
              key={index}
              image={item.image_url}
              title={item.title}
              item={item}
            />
          ))}
        </TopAni>
      </HomeWrapper>
    </AnimeContext.Consumer>
  );
}

const HomeWrapper = styled.div`
  height: 100%;
  padding: 6rem 4.5rem;
  color: ${props => props.theme.colors.white};
`

const TopAni = styled.div`
  max-width: 1200px;
  margin: 0 auto;
  display: grid;
  grid-gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  grid-template-rows: auto;
  padding: 1rem;
`

const TopAniTitle = styled.h2`
  grid-column: 1 / -1;
  justify-self: start;
`

export default HomePage

If I move my AnimeProvider below the Header , I am able to view the header like so:

 return (
      <Router>
          <ThemeProvider theme={theme}>
            <AppWrapper>
              <Header />
              <Switch>
        <AnimeProvider>
                <Route path='/' exact component={HomePage} />
                <Route path='/dashboard' exact component={AnimeCard} />
                <Route path='/:animeId' component={AnimeDetails} />
      </AnimeProvider>
              </Switch>
            </AppWrapper>
          </ThemeProvider>
        </Router>
    );

So I'm either missing something crucial or I'm not understanding how Context works and/or React Router.

You have a typo in AnimeProvider - it should render {props.children} not {props.childen}

AnimeContext.Consumer requires a function as a child, also if you are using useContext then there is no need to wrap your component with AnimeContext.Consumer in first place.

Also since your provider pass down an object, then useContext would return an object, so you need object destructuring instead of array destructuring.

const HomePage = () => {
  const { topTv, topAiring, topUpcoming } = useContext(AnimeContext)

  return (
     <HomeWrapper>
       <TopAni>
        {topTv.length > 0 ? <TopAniTitle>Top TV</TopAniTitle> : null}
        {topTv.map((item, index) => (
          <TopAnime
            key={index}
            image={item.image_url}
            title={item.title}
            item={item}
          />
        ))}
      </TopAni>
      ...
      </HomeWrapper>
  }
}

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