允许组件检测 Gatsby 中的路由变化

luk*_*mcd 10 reactjs gatsby

在 Gatsby 项目中,我有一个标题组件,它在每个页面上都是持久的。标题有一个模式来显示导航。每当路线更改时,我都需要将 isOpen 状态设置为 false,以便导航模式关闭。由于路线不仅可以通过单击模式中的链接来更改,还可以通过使用浏览器上的后退按钮来更改,因此我不希望在链接上使用事件来关闭模式。

在 Gatsby 中,我可以使用 gatsby-browser.js 中的 onRouteUpdate 来检测路由更改,这很有效。但是我需要将事件传递给我的组件,这就是我遇到困难的地方。我已经简化了下面的代码来显示设置。

gatsby-browser.js:

import React from "react"
import Layout from "./src/components/layout"

export const wrapPageElement = ({ element, props }) => {
  return <Layout {...props}>{element}</Layout>
}

export const onRouteUpdate = () => {
  console.log("onRouteUpdate") // this works
}
Run Code Online (Sandbox Code Playgroud)

布局.js:

import React from "react"

import Header from "./header"
import Footer from "./footer"

const Layout = ({ children }) => (
    <>
      <Header />
      <main>
        {children}
      </main>
      <Footer />
    </>
  )

export default Layout
Run Code Online (Sandbox Code Playgroud)

头文件.js:

import React, { useState } from "react"

const Header = () => {
  const [isOpen, setIsOpen] = useState(null)
  const toggleState = ({ props }) => {
    let status
    if (props) status = props.status
    else status = !isOpen
    setIsOpen(status)
  }
  return (
    <header>
      <div>This is the header</div>
      <button onClick={toggleState}>Toggle Open/Close</button>
      <button onClick={toggleState({ status: false })}>This will always close</button>
      /* logic here uses isOpen state to determine display */
    </header>
  )
}

export default Header
Run Code Online (Sandbox Code Playgroud)

小智 14

我解决这个问题的首选方法是使用Gatsby使用的未记录的globalHistoryfrom @reach/router。

import { globalHistory } from '@reach/router'

useEffect(() => {
  return globalHistory.listen(({ action }) => {
    if (action === 'PUSH') setIsOpen(false)
  })
}, [setIsOpen])
Run Code Online (Sandbox Code Playgroud)

现在每当您切换路线时,上述效果都会触发。

来源。


luk*_*mcd 9

我想出了一个解决我自己问题的方法,所以我想我会分享。任何意见/改进总是受欢迎的。

首先,我们不需要在 gatsby-broser.js 中使用“onRouteUpdate”,所以让我们删除它:

/* gatsby-browser.js */
import React from "react"
import Layout from "./src/components/layout"

export const wrapPageElement = ({ element, props }) => {
  return <Layout {...props}>{element}</Layout>
}
Run Code Online (Sandbox Code Playgroud)

然后,在 layout.js 中确保将位置传递给标题:

/* layout.js */
import React from "react"

import Header from "./header"
import Footer from "./footer"

const Layout = ({ children, location }) => (
    <>
      <Header location={location} />
      <main>
        {children}
      </main>
      <Footer />
    </>
  )

export default Layout
Run Code Online (Sandbox Code Playgroud)

最后,在 header.js 中,使用 useRef 钩子将位置存储在对 header 元素的引用中。useEffect 钩子会在路由改变时被触发,所以我们可以用它来比较:

/* header.js */
import React, { useState, useEffect, useRef } from "react"

const Header = () => {
  const [isOpen, setIsOpen] = useState(null)
  const myRef = useRef({
    location: null,
  })
  useEffect(() => {
    // set the location on initial load
    if (!myRef.current.location) myRef.current.location = location
    // then make sure dialog is closed on route change
    else if (myRef.current.location !== location) {
      if (isOpen) toggleState({ status: false })
      myRef.current.location = location
    }
  })
  const toggleState = ({ props }) => {
    let status
    if (props) status = props.status
    else status = !isOpen
    setIsOpen(status)
  }
  return (
    <header ref={myRef}>
      <div>This is the header</div>
      <button onClick={toggleState}>Toggle Open/Close</button>
      <button onClick={toggleState({ status: false })}>This will always close</button>
    </header>
  )
}

export default Header
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助任何寻找类似功能的人。