react-router-dom <Link> 未更新页面

IT技术 reactjs react-redux react-router
2021-05-19 07:58:32

问题描述:

通过链接标签更改此 url 的 id(仅限数字)不会更新页面(但会更改地址栏中的 url)。之后点击刷新将显示更新的页面。

http://localhost:8080/video/id/7564

右键单击以在新选项卡中打开链接,或将链接路径更改为完全不同的页面,按预期工作。

我的 app.js 文件

import React from 'react'
import { Router, Route, Switch } from 'react-router-dom'
import RenderHomepage from '../components/homePage/RenderHomepage'
import RenderChannelPage from '../components/channelPage/RenderChannelPage'
import RenderVideoPage from '../components/videoPage/RenderVideoPage'
import RenderSearchPage from '../components/searchPage/RenderSearchPage'
import PageNotFound from '../components/PageNotFound'
import history from '../history'

const App = () => {
  return ( 
    <div>
      <Router history={history}>
        <Switch>
          <Route path="/" exact component={RenderHomepage} /> 
          <Route path="/channel" component={RenderChannelPage} /> 
          <Route path="/video/id" component={RenderVideoPage} /> 
          <Route path="/search" component={RenderSearchPage} /> 
          <Route path="/404" exact component={PageNotFound} />
          <Route component={PageNotFound} />
        </Switch>
      </Router>
    </div>
  )
}

export default App

UpNextVideos 组件中的链接标签:

import React from 'react'
import { Link } from 'react-router-dom'

...
  <Link to={{pathname: vid.id}}> 
    <h3 className={`${p}-sidebar-grid-video-title`}>{capitalizeFirstLetter(vid.tags)}</h3>
  </Link>
...

有问题的组件如何嵌套:

<RenderVideoPage>
  <VideoPage>
    <UpNextVideos>

RenderVideoPage 组件:

import React from 'react'
import VideoPage from './VideoPage'
import Header from '../Header'
import HeaderMobile from '../HeaderMobile'
import FooterMobile from '../FooterMobile'
import ActivityFeed from '../ActivityFeed'

const RenderVideoPage = () => {
  return (
    <div className="videoPage-body">
      <HeaderMobile />
      <Header />
      <ActivityFeed page={'home'} />
      <VideoPage />
      <FooterMobile page={'video'} />
    </div>
  )
}

export default RenderVideoPage

视频页面组件:

import React, { useEffect, useState } from 'react'
import axios from 'axios'
import history from '../../history'
import handleMediaQueries from './containers/handleMediaQueries'
import setDislikes from './containers/setDislikes'

import NewSubscribers from './NewSubscribers'
import CommentSection from './CommentSection'
import UpNextVideos from './UpNextVideos'
import DescriptionBox from './DescriptionBox'
import VideoNotFound from './VideoNotFound'

import { fetchVideoFromID, fetchPictureFromID } from '../../containers/api'
import { thumbsUp, thumbsDown } from '../svgs'

import { 
  abbreviateNumber, 
  capitalizeFirstLetter, 
  randomDate } from '../../containers/helperFunctions'

const VideoPage = () => {
  const [p, setPrefix] = useState("videoPage")
  const [state, setState] = useState({
    loading: true,
    error: false
  })

  useEffect(() => {
    if (state.loading) extractDataFromUrl()
    else handleMediaQueries()
  }, [state.loading])

  const fetchVideo = async (id, picAuthorID) => {
    let response = await fetchVideoFromID(id)
    if (!response) setState(prevState => ({...prevState, error: true}))
    else mapVideoResponseToHTML(response.data.hits, picAuthorID)
  }

  const mapVideoResponseToHTML = (response, picAuthorID) => {
    let responseAsHtml = response.map(vid => {
      return {
        video: 
        <div className={`${p}-video-wrapper posRelative`} key={vid.id}>
          <a className={`${p}-pixabay-src`} href={vid.pageURL}>?</a>
          <video 
            poster="https://i.imgur.com/Us5ckqm.jpg" 
            className={`${p}-video clickable`} 
            src={vid.videos.large.url} 
            controls autoPlay>
          </video>
          <div className={`${p}-video-info-wrapper`}>  
            <div className={`${p}-video-title-box`}>
              <h1 className={`${p}-video-title`}>{capitalizeFirstLetter(vid.tags)}</h1>
              <span className={`${p}-video-views`}>{abbreviateNumber(Number(vid.downloads).toLocaleString())} views</span>
              <span className={`${p}-video-date`}>{randomDate()}</span>
            </div>
            <div className={`${p}-video-options`}>
              <div className="thumbs">
                <div className={`${p}-video-options-thumbsUp`}>{thumbsUp(20)} &nbsp; 
                  <span className={`${p}-video-options-thumbsUp-text`}>{abbreviateNumber(vid.likes)}</span>
                </div>
                <div className={`${p}-video-options-thumbsDown`}>{thumbsDown(20)} &nbsp; 
                  <span className={`${p}-video-options-thumbsDown-text`}>{setDislikes(vid.likes)}</span>
                </div>
                <div className={`${p}-video-options-likebar`}></div>
              </div>
              <span className={`${p}-video-options-share`}>Share</span>
              <span className={`${p}-video-options-save`}>Save</span>
              <span className={`${p}-video-options-ellipses`}>...</span>
            </div>
          </div>
        </div>,
        authorFollowers: vid.views,
        vidAuthorID: vid.id,
        author: picAuthorID ? 'Loading' : vid.user,
        authorAvatar: picAuthorID ? null : vid.userImageURL,
        views: vid.downloads
      }
    })
    responseAsHtml = responseAsHtml[0]
    setState(prevState => ({...prevState, ...responseAsHtml, loading: false}))
    if (picAuthorID) fetchAuthorAvatar(picAuthorID)
  }

  const extractDataFromUrl = () => {
    const currentURL = window.location.href
    const urlAsArray = currentURL.split('/')
    const urlID = urlAsArray[5].split('-')
    const videoID = urlID[0]
    const picAuthorID = urlID[1]

    // Author avatars are random except on the home page. 
    // if url isnt from homepage, then use videoID
    // if url is from homepage, send that avatarID
    if (urlID.includes('000')) {
      fetchVideo(videoID)
    } else {
      setState(prevState => ({...prevState, picAuthorID: picAuthorID}))
      fetchVideo(videoID, picAuthorID)
    }
  }

  const fetchAuthorAvatar = async (id) => {
    const response = await fetchPictureFromID(id)
    const authorName = response.data.hits[0].user
    const authorAvatar = response.data.hits[0].previewURL
    setState(prevState => ({
      ...prevState, 
      authorAvatar: authorAvatar, 
      author: capitalizeFirstLetter(authorName)
    }))
  }

  return (
    <div>
      { state.error ? <VideoNotFound /> : null}
      { state.loading === true ? null
        : 
        <div className={`${p}-page-wrapper`}>
          <main className={`${p}-main`}>
            {state.video}
            <DescriptionBox props={state} />
            <div className={`${p}-suggested-videos-mobile`}></div>

            <div className={`${p}-new-subscribers-wrapper`}>
              <h2 className={`${p}-new-subscribers-text`}>{`New Subscribers to ${state.author}`}</h2>
              <NewSubscribers />
            </div>
            <div className={`${p}-comment-section`}>
              <CommentSection views={state.views}/>
            </div>
          </main>
          <aside className={`${p}-sidebar`}>
           <UpNextVideos />
          </aside>
        </div>
      }
    </div>
  )
}

export default VideoPage

UpNextVideos 组件:

import React, { useEffect, useState, useRef, useCallback } from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'
import { videoQuery } from '../../words'
import { fetchVideos } from '../../containers/api'
import { 
  capitalizeFirstLetter, 
  uuid,
  getRandom,
  abbreviateNumber
} from '../../containers/helperFunctions'

const UpNextVideos = () => {
  const [p, setPrefix] = useState("videoPage")
  const [nextVideos, setNextVideos] = useState([])

  useEffect(() => {
    fetchUpNextVideos(15, getRandom(videoQuery))
  }, [])

  // INFINITE SCROLL
  const observer = useRef()
  const lastUpNextVideo = useCallback(lastVideoNode => {

    // Re-hookup observer to last post, to include fetch data callback
    if (observer.current) observer.current.disconnect()
    observer.current = new IntersectionObserver(entries => {
      const lastVideo = entries[0]
        if (lastVideo.isIntersecting && window.innerWidth <= 1000) {
          document.querySelector('.videoPage-show-more-button').classList.add('show')
        }
        else if (lastVideo.isIntersecting && window.innerWidth > 1000) {
          document.querySelector('.videoPage-show-more-button').classList.remove('show')
          fetchUpNextVideos(20, getRandom(videoQuery))
      }
    })
    if (lastVideoNode) observer.current.observe(lastVideoNode)
  })

  const fetchUpNextVideos = async (amount, query) => {
    let response = await fetchVideos(amount, ...Array(2), query)
    response = response.data.hits

    const responseAsHtml = response.map((vid, index) => {
      return (
        <div className={`${p}-sidebar-grid-video-wrapper`} key={uuid()} ref={response.length === index + 1 ? lastUpNextVideo : null}>
          <div className={`${p}-sidebar-grid-video`}>
            <a href={`/video/id/${vid.id}-000`}>
              <video 
                className={`${p}-upnext-video`} 
                onMouseOver={event => event.target.play()}
                onMouseOut={event => event.target.pause()}
                src={`${vid.videos.tiny.url}#t=1`}
                muted >
              </video>
            </a>
          </div>
          <a href={`/video/id/${vid.id}`}>
            <h3 className={`${p}-sidebar-grid-video-title`}>{capitalizeFirstLetter(vid.tags)}</h3>
          </a>
          <a href={`/channel/000${vid.id}`}>
            <p className={`${p}-sidebar-grid-video-author`}>{vid.user}</p>
          </a>
          <p className={`${p}-sidebar-grid-video-views-text`}>{abbreviateNumber(vid.downloads)} views</p>
        </div>
      )
    })
    setNextVideos(prevState => ([...prevState, ...responseAsHtml]))
  }

  return (
    <div>
      <div className={`${p}-sidebar-text-top`}>
        <span className={`${p}-sidebar-text-upnext`}>Up next</span>
        <span className={`${p}-sidebar-text-autoplay`}>Autoplay</span>
      </div>
      <div className={`${p}-sidebar-grid-wrapper`}>
        {nextVideos}
      </div> 
      <button 
        className={`${p}-show-more-button`} 
        onMouseDown={() => fetchUpNextVideos(15, getRandom(videoQuery))}>
        Show More
      </button>
    </div>
  )
}

export default UpNextVideos

我试过的:

  • <Link>标签包裹<Router history={history} />
  • <Link>标签包裹<BrowserRouter>
  • 包装导出语句 withRouter(UpNextVideos)
  • 使用普通字符串而不是对象,如react-router-docs 中所述
1个回答

好的,我相信这个问题出在您的VideoPage组件上。

useEffect(() => {
  if (state.loading) extractDataFromUrl()
  else handleMediaQueries()
}, [state.loading]);

state.loading当组件安装时,您只有一次true。这只会处理您的 URL 一次,因此当 URL 更改时,此组件不会意识到它。

这是您目前的路线

<Route path="/video/id" component={RenderVideoPage} />

现在假设您的 URL 的形状为“/video/id/”,那么您可以定义您的路由以具有参数

 <Route path="/video/id/:videoId" component={RenderVideoPage} /> 

如果您使用react-router-dom's withRouterHOC包装此组件,您可以轻松获取id路径参数并将其添加到效果中以重新计算所有视频数据。

export default withRouter(VideoPage)

withRouter喷射locationmatchhistoryprops从最近的Route祖先。这是获取 id 参数并在其值更新时触发效果的示例。

const VideoPage = ({ match }) => {
  const { params } = match;

  useEffect(() => { /* do something with new id */ }, [params.videoId]);

}