Browser Router 无论是否放在其范围之外,都会将状态/上下文值重置为默认值。

huangapple go评论70阅读模式
英文:

Browser Router resetting state/context value to default regardless of it being placed outside of its scope

问题

我正在尝试使用浏览器路由(Browser Router)创建一个简单的页面。导航栏包含7个类别,其中6个链接到"/category/:categoryName",而1个链接回到主页。我需要跟踪所选类别的名称,以便获取正确的数据和一些其他小事情,因此我创建了一个单独的.tsx文件,它将状态用作上下文的值。上下文提供程序位于路由器的范围之上(就我对所有事情的理解而言)。当单击任何图标时,路由器会正确更改URL,但在每种情况下,都会导致页面刷新(控制台中的日志被清除并从头开始),并且状态/上下文值被重置为默认值。我被困在这个问题上几天了,无法弄清楚是什么原因导致了这种行为。在绝望的一刻,我尝试切换到哈希路由(hash router),代码可以完美运行,但继续使用它而不是浏览器路由(browser router)会使我的代码变得更加复杂,因为我无法简单地链接回到主页。我提供了相关的代码,Nav组件是从Header组件内部渲染的。

上下文文件:

import { createContext, useState, useEffect } from "react";

export const SelectedCategoryContext = createContext({
    selectedCategory: "home",
    setSelectedCategory: (categoryName: string) => {}
});

const SelectedCategoryProvider = ({ children }: any) => {
    const [selectedCategory, setSelectedCategory] = useState("home");

    useEffect(() => {
        console.log("SC context", selectedCategory);
    }, [selectedCategory]);

    return (
        <SelectedCategoryContext.Provider value={{ selectedCategory, setSelectedCategory }}>
            {children}
        </SelectedCategoryContext.Provider>
    );
};

export default SelectedCategoryProvider;

App.tsx:

import "./App.scss";
import Header from "./components/Header";
import { useEffect, useState } from "react";
import { Routes, Route, BrowserRouter } from "react-router-dom";
import Homepage from "./pages/Homepage";
import Category from "./pages/Category";
import SelectedCategoryProvider from "./components/SelectedCategoryContext";

const App = () => {
    return (
        <SelectedCategoryProvider>
            <BrowserRouter>
                <Header />
                <Routes>
                    <Route path="/" element={<Homepage />} />
                    <Route
                        path="/category/:categoryName"
                        element={<Category />}
                    />
                </Routes>
            </BrowserRouter>
        </SelectedCategoryProvider>
    );
};

export default App;

从Nav.tsx中剪切的部分,因为其余的代码只是不同的类别名称和svg重复:

import { MouseEventHandler, useContext, useEffect } from "react";
import "./nav.scss";
import { Link } from "react-router-dom";
import { ToggleMenuContext } from "../../util/context";
import { SelectedCategoryContext } from "../SelectedCategoryContext";

const Nav = () => {
    const { selectedCategory, setSelectedCategory } = useContext(
        SelectedCategoryContext
    );

    const { setIsMenuOpen, setIsHamburgerClicked } =
        useContext(ToggleMenuContext);

    const handleNavClick: MouseEventHandler<HTMLLIElement> = (e) => {
        e.stopPropagation();
        setSelectedCategory(e.currentTarget.id);
        setIsMenuOpen(false);
        setIsHamburgerClicked(false);
    };

    useEffect(() => {
        console.log(selectedCategory);
    }, [selectedCategory]);

    return (
        <nav>
            <ul>
                <Link to="/">
                    <li
                        id="home"
                        onClick={handleNavClick}
                        className={
                            selectedCategory === "home"
                                ? "li selected-nav"
                                : "li"
                        }
                    >
                        <svg
                            width="20"
                            height="20"
                            viewBox="0 0 20 20"
                            fill="none"
                            xmlns="http://www.w3.org/2000/svg"
                        >
                            <path
                                fillRule="evenodd"
                                clipRule="evenodd"
                                d="M19.7557 9.41083L10.589... 9.64916 19.7557 9.41083Z"
                                fill="#8d8d8c"
                            />
                        </svg>
                        <span>Home</span>
                    </li>
                </Link>
                <Link to="/category/general">
                    <li
                        id="general"
                        onClick={handleNavClick}
                        className={
                            selectedCategory === "general"
                                ? "li selected-nav"
                                : "li"
                        }
                    >
                        <svg
                            width="20"
                            height="20"
                            viewBox="0 0 20 20"
                            fill="none"
                            xmlns="http://www.w3.org/2000/svg"
                        >
                            <path
                                id="News"
                                fillRule="evenodd"
                                clipRule="evenodd"
                                d="M17.5 5.83332C18.8807.......15.0392Z"
                                fill="#8d8d8c"
                            />
                        </svg>
                        <span>General</span>
                    </li>
                </Link>

我尝试将上下文提供程序放在index.tsx中并将所需的状态保持在App.tsx的顶层,但没有任何变化。我不了解Redux,也没有时间在完成这个项目之前学习它。如果您有任何建议或想法,请帮助我。

英文:

I'm trying to create a simple page using Browser Router. Navigation consist of 7 categories, 6 of which link to &quot;/category/:categoryName&quot;, and 1 links back to root. I need to keep track of the selected category name in order to fetch correct data and some other minor things, so I created a separate .tsx file which utilizes state as value for context. Context provider is placed above router's scope (as far as my understanding of everything goes). When any icon is clicked, router correctly changes the URL, however in every case something is causing the page to refresh(logs in console get erased and start again from scratch) and the state/context value gets reset back to default. I've been stuck on this for days and can't figure out what is causing this behaviour. I tried to switch to hash router in a moment of desparation, and the code works perfectly with it, however continuing to use it instead of browser router complicates my code more than necessary since i can't simply link back to root. I'm providing the relevant code, Nav component is rendered from inside the Header component

Context file:

import { createContext , useState, useEffect} from &quot;react&quot;;
export const SelectedCategoryContext = createContext({
selectedCategory: &quot;home&quot;,
setSelectedCategory: (categoryName: string) =&gt; {}
});
const SelectedCategoryProvider = ({children}: any) =&gt; {
const [selectedCategory, setSelectedCategory] = useState(&quot;home&quot;);
useEffect(() =&gt; {
console.log(&quot;SC context&quot;, selectedCategory);
}, [selectedCategory]);
return (
&lt;SelectedCategoryContext.Provider value={{selectedCategory, setSelectedCategory}}&gt;
{children}
&lt;/SelectedCategoryContext.Provider&gt;
);
};
export default SelectedCategoryProvider;

App.tsx:

import &quot;./App.scss&quot;;
import Header from &quot;./components/Header&quot;;
import { useEffect, useState } from &quot;react&quot;;
import { Routes, Route, BrowserRouter } from &quot;react-router-dom&quot;;
import Homepage from &quot;./pages/Homepage&quot;;
import Category from &quot;./pages/Category&quot;;
import SelectedCategoryProvider from &quot;./components/SelectedCategoryContext&quot;;
const App = () =&gt; {
return (
&lt;SelectedCategoryProvider
&gt;
&lt;BrowserRouter&gt;
&lt;Header /&gt;
&lt;Routes&gt;
&lt;Route path=&quot;/&quot; element={&lt;Homepage /&gt;} /&gt;
&lt;Route
path=&quot;/category/:categoryName&quot;
element={&lt;Category /&gt;}
/&gt;
&lt;/Routes&gt;
&lt;/BrowserRouter&gt;
&lt;/SelectedCategoryProvider&gt;
);
};
export default App;

**Segment cut out from Nav.tsx as the remainder of code repeats itself just with different category names & svgs:

import { MouseEventHandler, useContext, useEffect } from &quot;react&quot;;
import &quot;./nav.scss&quot;;
import { Link } from &quot;react-router-dom&quot;;
import {ToggleMenuContext } from &quot;../../util/context&quot;;
import { SelectedCategoryContext } from &quot;../SelectedCategoryContext&quot;;
const Nav = () =&gt; {
const { selectedCategory, setSelectedCategory } = useContext(
SelectedCategoryContext
);
const { setIsMenuOpen, setIsHamburgerClicked } =
useContext(ToggleMenuContext);
const handleNavClick: MouseEventHandler&lt;HTMLLIElement&gt; = (e) =&gt; {
e.stopPropagation();
setSelectedCategory(e.currentTarget.id);
setIsMenuOpen(false);
setIsHamburgerClicked(false);
};
useEffect(() =&gt; {
console.log(selectedCategory);
}, [selectedCategory]);
return (
&lt;nav&gt;
&lt;ul&gt;
&lt;Link to=&quot;/&quot;&gt;
&lt;li
id=&quot;home&quot;
onClick={handleNavClick}
className={
selectedCategory === &quot;home&quot;
? &quot;li selected-nav&quot;
: &quot;li&quot;
}
&gt;
&lt;svg
width=&quot;20&quot;
height=&quot;20&quot;
viewBox=&quot;0 0 20 20&quot;
fill=&quot;none&quot;
xmlns=&quot;http://www.w3.org/2000/svg&quot;
&gt;
&lt;path
fillRule=&quot;evenodd&quot;
clipRule=&quot;evenodd&quot;
d=&quot;M19.7557 9.41083L10.589... 9.64916 19.7557 9.41083Z&quot;
fill=&quot;#8d8d8c&quot;
/&gt;
&lt;/svg&gt;
&lt;span&gt;Home&lt;/span&gt;
&lt;/li&gt;
&lt;/Link&gt;
&lt;Link to=&quot;/category/general&quot;&gt;
&lt;li
id=&quot;general&quot;
onClick={handleNavClick}
className={
selectedCategory === &quot;general&quot;
? &quot;li selected-nav&quot;
: &quot;li&quot;
}
&gt;
&lt;svg
width=&quot;20&quot;
height=&quot;20&quot;
viewBox=&quot;0 0 20 20&quot;
fill=&quot;none&quot;
xmlns=&quot;http://www.w3.org/2000/svg&quot;
&gt;
&lt;path
id=&quot;News&quot;
fillRule=&quot;evenodd&quot;
clipRule=&quot;evenodd&quot;
d=&quot;M17.5 5.83332C18.8807.......15.0392Z&quot;
fill=&quot;#8d8d8c&quot;
/&gt;
&lt;/svg&gt;
&lt;span&gt;General&lt;/span&gt;
&lt;/li&gt;
&lt;/Link&gt;

I've tried placing context provider in index.tsx and keeping needed state at the top level of App.tsx but nothing changed. I don't know Redux yet and don't have time to learn it before having to finish this project. Please help me if you have any suggestions or ideas.

答案1

得分: 1

根据您的请求,这是代码的翻译部分:

在我最终看到的代码中,导致应用重新装载并重置状态的唯一问题是Link组件被渲染为ul元素的子元素。应该将li渲染为ul的子元素。交换liLink的位置后,组件不再丢失状态。

另外,看起来SelectedCategoryContext上下文和提供程序仅用于使导航栏突出显示当前的类别磁贴。由于这些磁贴与链接耦合,基本上是重复了URL路径匹配。我建议使用NavLink组件,并利用其isActive状态来知道哪个链接/磁贴/卡片是当前匹配的。

示例:

import { MouseEventHandler, useContext } from "react";
import "./nav.scss";
import { NavLink } from "react-router-dom";
import { ToggleMenuContext } from "../../util/context";

const Nav = () => {
  const { setIsMenuOpen, setIsHamburgerClicked } =
    useContext(ToggleMenuContext);

  const handleNavClick: MouseEventHandler<HTMLAnchorElement> = (e) => {
    setIsMenuOpen(false);
    setIsHamburgerClicked(false);
  };

  return (
    <nav>
      <ul>
        <li>
          <NavLink
            onClick={handleNavClick}
            to="/"
            end
            className={({ isActive }) => (isActive ? "li selected-nav" : "li")}
          >
            ...
            <span>Home</span>
          </NavLink>
        </li>
        <li>
          <NavLink
            to="/category/general"
            onClick={handleNavClick}
            className={({ isActive }) => (isActive ? "li selected-nav" : "li")}
          >
            ...
            <span>General</span>
          </NavLink>
        </li>
        <li>
          <NavLink
            to="/category/business"
            onClick={handleNavClick}
            className={({ isActive }) => (isActive ? "li selected-nav" : "li")}
          >
            ...
            <span>Business</span>
          </NavLink>
        </li>
        <li>
          <NavLink
            to="/category/health"
            onClick={handleNavClick}
            className={({ isActive }) => (isActive ? "li selected-nav" : "li")}
          >
            ...
            <span>Health</span>
          </NavLink>
        </li>
        <li>
          <NavLink
            to="/category/science"
            onClick={handleNavClick}
            className={({ isActive }) => (isActive ? "li selected-nav" : "li")}
          >
            ...
            <span>Science</span>
          </NavLink>
        </li>
        <li>
          <NavLink
            to="/category/sports"
            onClick={handleNavClick}
            className={({ isActive }) => (isActive ? "li selected-nav" : "li")}
          >
            ...
            <span>Sports</span>
          </NavLink>
        </li>
        <li>
          <NavLink
            to="/category/technology"
            onClick={handleNavClick}
            className={({ isActive }) => (isActive ? "li selected-nav" : "li")}
          >
            ...
            <span>Technology</span>
          </NavLink>
        </li>
      </ul>
    </nav>
  );
};

export default Nav;

Category组件应该使用react-router-domuseParams钩子来访问当前的categoryName路由路径参数。

import { useContext, useEffect } from "react";
import "./category.scss";
import { useParams } from "react-router-dom";

const Category = () => {
  const { categoryName } = useParams();

  return (
    <>
      <h1>{categoryName}</h1>
    </>
  );
};

export default Category;
英文:

From what I could finally see in your code, the only issue I found that was causing the app to remount, and reset state, was that the Link component was being rendered as the child element of the ul element. The li is what should be rendered as the child of ul. When swapping the positions of li and Link the component no longer lost the state.

That said, it looks like the SelectedCategoryContext Context and provider exists only to allow the navbar to highlight the current category tile. Since the tiles are coupled to links it's a bit redundant to basically duplicate the URL path matching. I suggest using the NavLink component and utilizing its isActive state to know which link/tile/card is the actively matched one.

Example:

import { MouseEventHandler, useContext } from &quot;react&quot;;
import &quot;./nav.scss&quot;;
import { NavLink } from &quot;react-router-dom&quot;;
import { ToggleMenuContext } from &quot;../../util/context&quot;;

const Nav = () =&gt; {
  const { setIsMenuOpen, setIsHamburgerClicked } =
    useContext(ToggleMenuContext);

  const handleNavClick: MouseEventHandler&lt;HTMLAnchorElement&gt; = (e) =&gt; {
    setIsMenuOpen(false);
    setIsHamburgerClicked(false);
  };

  return (
    &lt;nav&gt;
      &lt;ul&gt;
        &lt;li&gt;
          &lt;NavLink
            onClick={handleNavClick}
            to=&quot;/&quot;
            end
            className={({ isActive }) =&gt; (isActive ? &quot;li selected-nav&quot; : &quot;li&quot;)}
          &gt;
            ...
            &lt;span&gt;Home&lt;/span&gt;
          &lt;/NavLink&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;NavLink
            to=&quot;/category/general&quot;
            onClick={handleNavClick}
            className={({ isActive }) =&gt; (isActive ? &quot;li selected-nav&quot; : &quot;li&quot;)}
          &gt;
            ...
            &lt;span&gt;General&lt;/span&gt;
          &lt;/NavLink&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;NavLink
            to=&quot;/category/business&quot;
            onClick={handleNavClick}
            className={({ isActive }) =&gt; (isActive ? &quot;li selected-nav&quot; : &quot;li&quot;)}
          &gt;
            ...
            &lt;span&gt;Business&lt;/span&gt;
          &lt;/NavLink&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;NavLink
            to=&quot;/category/health&quot;
            onClick={handleNavClick}
            className={({ isActive }) =&gt; (isActive ? &quot;li selected-nav&quot; : &quot;li&quot;)}
          &gt;
            ...
            &lt;span&gt;Health&lt;/span&gt;
          &lt;/NavLink&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;NavLink
            to=&quot;/category/science&quot;
            onClick={handleNavClick}
            className={({ isActive }) =&gt; (isActive ? &quot;li selected-nav&quot; : &quot;li&quot;)}
          &gt;
            ...
            &lt;span&gt;Science&lt;/span&gt;
          &lt;/NavLink&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;NavLink
            to=&quot;/category/sports&quot;
            onClick={handleNavClick}
            className={({ isActive }) =&gt; (isActive ? &quot;li selected-nav&quot; : &quot;li&quot;)}
          &gt;
            ...
            &lt;span&gt;Sports&lt;/span&gt;
          &lt;/NavLink&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;NavLink
            to=&quot;/category/technology&quot;
            onClick={handleNavClick}
            className={({ isActive }) =&gt; (isActive ? &quot;li selected-nav&quot; : &quot;li&quot;)}
          &gt;
            ...
            &lt;span&gt;Technology&lt;/span&gt;
          &lt;/NavLink&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
    &lt;/nav&gt;
  );
};

export default Nav;

The Category component should use the useParams hook from react-router-dom to access the current categoryName route path param.

import { useContext, useEffect } from &quot;react&quot;;
import &quot;./category.scss&quot;;
import { useParams } from &quot;react-router-dom&quot;;

const Category = () =&gt; {
  const { categoryName } = useParams();

  return (
    &lt;&gt;
      &lt;h1&gt;{categoryName}&lt;/h1&gt;
    &lt;/&gt;
  );
};

export default Category;

huangapple
  • 本文由 发表于 2023年6月12日 22:46:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76457802.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定