简体   繁体   English

反应路由器链接更改 URL 但不呈现组件 - Rest 国家 API

[英]React Router Link changes URL but doesn't render Component - Rest Countries API

I am building an app following the Rest Countries API challenge from frontendmentor ( https://www.frontendmentor.io/challenges/rest-countries-api-with-color-theme-switcher-5cacc469fec04111f7b848ca ). I am building an app following the Rest Countries API challenge from frontendmentor ( https://www.frontendmentor.io/challenges/rest-countries-api-with-color-theme-switcher-5cacc469fec04111f7b848ca ). I have run into a problem.我遇到了一个问题。 When clicking on the router link in countryDetail.js, the url changes but the component doesn't get re-rendered unless the page is refreshed.单击 countryDetail.js 中的路由器链接时,url 会发生变化,但除非刷新页面,否则组件不会重新呈现。

CountryDetails.js CountryDetails.js

import React, { useState, useEffect } from "react";
import axios from "axios";
import {Link} from "react-router-dom";
import {CountryDetailStyles, ImgContainer, CountryInfo, CountryInfoDetails, CountryDetailsWrapper, CountryInfoDetailsInner, CountryBorders, LinkWrapper} from "../styles/CountryDetailStyles";

function CountryDetail({match, history}) {

  const [item, setItem] = useState([]);
  const [allCountries, setAllCountries] = useState([]);

  useEffect(() => {
     fetchItem();
     setBorderCountries();
  }, []);

  const fetchItem = async () => {
    const response = await axios.get(`https://restcountries.eu/rest/v2/name/${match.params.name}?fullText=true`);
    setItem(response.data)
  }

  const setBorderCountries = async () => {
    const response = await axios.get(`https://restcountries.eu/rest/v2/all`);
    setAllCountries(response.data)
  }

  // get borders full name using alpha3code
  const getBorderCountryName = (allCountries, border) => {
    const matchingCountry = allCountries.find(country => {
      return country.alpha3Code === border;
    })

     return matchingCountry.name;
  }
    
  return (
       <CountryDetailStyles>
           <Link className="country-links" to={"/"}>
             <LinkWrapper>
             <p><i className="fas fa-arrow-left"></i>Go Back</p>
             </LinkWrapper>
             </Link>
           {
            item.length > 0 && item.map(i => (   
               <CountryDetailsWrapper>           
               <ImgContainer flag={i.flag}>
               </ImgContainer>
               <CountryInfo>
                   <h1>{i.name}</h1>
                   <CountryInfoDetails>
                       <CountryInfoDetailsInner>
                       <p><span>Native Name: </span>{i.nativeName}</p>
                       <p><span>Population: </span>{i.population.toLocaleString()}</p>
                       <p><span>Region: </span>{i.region.length > 0 ? i.region : 'N/A'}</p>
                       <p><span>Sub Region: </span>{i.subregion.length > 0 ? i.subregion : 'N/A'}</p>
                       <p><span>Capital: </span>{i.capital.length > 0 ? i.capital : 'N/A'}</p>
                       </CountryInfoDetailsInner>
                       <CountryInfoDetailsInner className="second">
                       <p><span>Top Level Domain: </span>{i.topLevelDomain}</p>
                       <p><span>Currencies: </span>{i.currencies[0].name}</p>
                       <p><span>Languages: </span>{i.languages.map(lang => lang.name).join(", ")}</p>
                       </CountryInfoDetailsInner>
                   </CountryInfoDetails>
                   <CountryBorders>
                       <p><span>Border Countries:</span></p>
                    {allCountries.length > 0 && i.borders.map(border => {
                      const borderName = getBorderCountryName(allCountries, border);
                      return (<Link to={`/country/${borderName.toLowerCase()}`}><button>{borderName}</button></Link>)
                    })}
                   </CountryBorders>
               </CountryInfo>
               </CountryDetailsWrapper>
           ))
           }
        </CountryDetailStyles>
  )
}

export default CountryDetail;

App.js应用程序.js

import './App.css';
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from './styles/themes';
import { GlobalStyles } from './styles/global';
import { useState } from 'react';
import Navbar from './components/Navbar';
import { useDarkMode } from './hooks/useDarkMode';
import CountryList from './components/CountryList';
import CountryDetail from './components/CountryDetail';
import {Route} from "react-router-dom";


function App() {
  const [theme, toggleTheme] = useDarkMode();
  const themeMode = theme === 'dark' ? darkTheme : lightTheme;

  return (
    <div className="App">
      <ThemeProvider theme={themeMode}>
        <GlobalStyles />
        <Navbar navTheme={theme} toggleTheme={toggleTheme} />
        <Route exact path="/" component={CountryList} />
        <Route exact path="/country/:name" component={CountryDetail} />
      </ThemeProvider>
    </div>
   
  );
}

export default App;

Index.js索引.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';

ReactDOM.render(
  <BrowserRouter>
    <App />
    </BrowserRouter>,
  document.getElementById('root')
);

 
reportWebVitals();

Issue问题

The issue seems to be that you are already on the "/country/:name" path and are clicking to visit another country.问题似乎是您已经"/country/:name"路径上,并且正在点击访问另一个国家。 The router correctly updates the URL in the address bar, but because CountryDetail is already mounted it neglects to recompute the item and allCountries state.路由器正确更新地址栏中的 URL,但由于CountryDetail已安装,它忽略重新计算itemallCountries This is because the useEffect hook only runs once when the component mounts.这是因为useEffect挂钩仅在组件挂载时运行一次。

Solution解决方案

The name param ( match.params.name ) is actually a dependency for the GET requests, it should be added to the useEffect hook's dependency array. name参数( match.params.name实际上是 GET 请求的依赖项,它应该添加到useEffect挂钩的依赖项数组中。

useEffect(() => {
 fetchItem();
 setBorderCountries();
}, [match.params.name]);

const fetchItem = async () => {
  const response = await axios.get(
    `https://restcountries.eu/rest/v2/name/${match.params.name}?fullText=true`
  );
  setItem(response.data);
}

Suggestion建议

It seems allCountries doesn't have this dependency, so I recommend splitting it into its own useEffect hook with empty dependency array so you fetch this data only once when the component mounts.似乎allCountries没有这种依赖关系,所以我建议将其拆分为自己的useEffect钩子和空依赖数组,以便在组件挂载时仅获取此数据一次。

useEffect(() => {
 setBorderCountries();
}, []);

useEffect(() => {
 fetchItem();
}, [match.params.name]);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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