【问题标题】:How to set a NavLink to active conditionally on dynamic links如何在动态链接上有条件地将 NavLink 设置为活动
【发布时间】:2021-06-06 22:59:11
【问题描述】:

在我的菜单中,我有一个导航链接 - 我的个人资料。当用户点击我的个人资料时,反应重定向到他们的个人资料。然后按预期将导航链接设置为活动状态,同样应用isActive 样式。

        <NavLink className="nav-link" to="/my_profile" activeStyle={isActive}>
          My Profile
        </NavLink>

用户可以通过导航至www.sitename.com/***username***(如 Twitter 或 Instagram)导航至用户的个人资料。

          <Route path="/:profile_name">
            <Profile />
          </Route>

当用户登录并通过此动态路径导航到他们自己的个人资料时,我希望将 我的个人资料 的导航链接设置为活动状态。但我只能弄清楚如何在点击我的个人资料时将我的个人资料设置为活动状态,而不是当用户通过地址栏导航到他们自己的个人资料时。 p>

当用户通过地址栏导航到他们的动态个人资料路径时,如何将 我的个人资料NavLink 设置为活动状态?

示例

在我的代码示例中,我将currentUser 硬编码为dash123,因此当我们导航到http://somedomain.com/dash123 时,Profile 组件将/dash123 识别为当前用户的个人资料并设置状态@987654332 @ 为真。当授权为true 时,Profile 呈现以显示“编辑您的个人资料”。当authorizedtrue 时,我是否也可以这样做,我的个人资料NavLink 设置为活动状态?

CodeSandbox

代码:

import React, { useState, useEffect } from "react";
import {
  NavLink,
  BrowserRouter as Router,
  Route,
  Switch,
  useParams
} from "react-router-dom";

function Nav() {
  const isActive = {
    fontWeight: "bold",
    backgroundColor: "lightgrey"
  };

  return (
    <ul className="navbar-nav mr-auto">
      <li className="nav-item">
        <NavLink className="nav-link" to="/Shop" activeStyle={isActive}>
          Shop
        </NavLink>
      </li>
      <li className="nav-item">
        <NavLink className="nav-link" to="/my_profile" activeStyle={isActive}>
          My Profile
        </NavLink>
      </li>
    </ul>
  );
}
function Shop(props) {
  return (
    <div>
      Lots of items to buy
      <br /> Shoes $4.99
      <br /> Carrots $9.99
      <br /> Teslas $800,000
    </div>
  );
}
function Profile(props) {
  let { profile_name } = useParams();
  const [profile, setProfile] = useState();
  const [authorized, setAuthorized] = useState(null);

  // fake calls to database
  const currentUser = () => {
    return { name: "Dashie" };
  };
  const userInfo = (username) => {
    const db = [
      { username: "dash123", name: "Dashie" },
      { username: "bob123", name: "Bob" }
    ];
    return db.find((userDoc) => userDoc.username === username);
  };

  useEffect(() => {
    if (props.profile === "currentUser") {
      setProfile(currentUser());
    } else {
      setProfile(userInfo(profile_name));
    }
  }, [profile_name, props.profile]);
  useEffect(() => {
    if (profile && profile.name === currentUser().name) {
      setAuthorized(true);
    }
  }, [profile]);

  return (
    <div>
      Profile:
      <br />
      {profile && <>Name: {profile.name}</>}
      <br />
      {authorized && profile && <>Edit your profile, {profile.name}</>}
    </div>
  );
}

export default function App() {
  return (
    <div className="App">
      <Router>
        <Nav />
        <Switch>
          <Route path="/shop">
            <Shop />
          </Route>
          <Route path="/my_profile">
            <Profile profile={"currentUser"} />
          </Route>
          <Route path="/:profile_name">
            <Profile />
          </Route>
        </Switch>
      </Router>
      <h2>Problem</h2>
      <p>
        If you click on <b>Shop</b>, as expected the <b>Shop</b> nav link will
        be highlighted.
        <br />
        <br /> Likewise, the <b>My Profile</b> nav link will be highlighted when
        we click on it and navigate to the user's profile.
        <br />
        <br /> But, we also want to have <b>My Profile</b> highlighted when we
        navigate to,{" "}
        <i>
          "https://whateverdomainyouron/<b>dash123</b>"
        </i>{" "}
        since this takes us to the current user's profile.
      </p>
    </div>
  );
}

【问题讨论】:

    标签: javascript reactjs react-router react-router-dom


    【解决方案1】:

    我采纳了 Drew Reese 的建议并对其进行了扩展以满足我的需求。

    我这样做是为了在满足isProfileRoutecurrentUsersProfile 的情况下启用“我的个人资料”链接。

            <NavLink
              isActive={() => isProfileRoute && currentUsersProfile}
              className="nav-link"
              to="/my_profile"
              activeStyle={isActive}
            >
    

    currentUsersProfile 在 AuthContext 上。

    const AuthContext = createContext({
      authorized: false,
      profile: null,
      currentUsersProfile: null,
      setAuthorized: () => {},
      setProfile: () => {},
      setCurrentUsersProfile: () => {},
    });
    

    当当前配置文件属于当前登录用户时,Profile 中的 currentUsersProfile 设置为 true。:

      useEffect(() => {
        if (profile?.name === currentUser().name) {
          setAuthorized(true);
          setCurrentUsersProfile(true);
        } else {
          setCurrentUsersProfile(false);
        }
      }, [profile]);
    

    我创建了一个包含所有路线的数组:

    const Routes = (
        <Switch>
        <Route path="/shop">
          <Shop />
        </Route>
        <Route path={"/pro"}>
          <Profile />
        </Route>
        <Route path={["/:profile_name", "/my_profile"]}>
          <Profile />
        </Route>
      </Switch>
    );
    const array = Routes.props.children.map((child) => child.props.path);
    // outputs: ['/shop', '/pro', ["/:profile_name", "/my_profile"]]
    

    Nav 中,我使用这个数组来检查用户当前所在的路由是否是配置文件路由(例如:'/dash123'、'/somename' 或固定路由'/my_profile')使用useMatchRoute

    function Nav() {
      const { currentUsersProfile } = useContext(AuthContext);
      const allNonProfileRoutes = array.slice(0, -1);
      let nonProfileRoute = useRouteMatch([...allNonProfileRoutes, { path: "/" }]);
    
      const isProfileRoute = !nonProfileRoute.isExact;
    
    

    codesandbox

    【讨论】:

      【解决方案2】:

      问题

      您的身份验证状态位于您的 Profile 组件中,因此无法在 Nav 组件中访问。

      解决方案

      重构您的代码以提升Nav 组件上方的身份验证状态并将其作为道具传递(或在上下文中使用)并相应地设置活动链接状态。以下解决方案使用 React 上下文。

      1. 创建一个AuthContext 和提供程序组件。

        const AuthContext = createContext({
          authorized: false,
          profile: null,
          setAuthorized: () => {},
          setProfile: () => {}
        });
        
        const AuthProvider = ({ children }) => {
          const [profile, setProfile] = useState();
          const [authorized, setAuthorized] = useState(null);
        
          const value = {
            authorized,
            profile,
            setAuthorized,
            setProfile
          };
        
          return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
        };
        

      AuthProvider包裹Router

          <AuthProvider>
            <Router>
              <Nav />
              <Switch>
                <Route path="/shop">
                  <Shop />
                </Route>
                <Route path="/my_profile">
                  <Profile profile={"currentUser"} />
                </Route>
                <Route path="/:profile_name">
                  <Profile />
                </Route>
              </Switch>
            </Router>
          </AuthProvider>
      
      1. 使用Profile 中的AuthContext 来设置身份验证状态。 IE。将旧的 useState 钩子换成 useContext 钩子。

        function Profile(props) {
          const { profile_name } = useParams();
          const { authorized, profile, setAuthorized, setProfile } = useContext(
            AuthContext
          );
        
          ...
        
          return (
            <div>
              Profile:
              <br />
              {profile && <>Name: {profile.name}</>}
              <br />
              {authorized && profile && <>Edit your profile, {profile.name}</>}
            </div>
          );
        }
        
      2. 使用Nav 中的AuthContext 来准备身份验证状态并相应地设置NavLink 活动状态。

        function Nav() {
          const { authorized } = useContext(
            AuthContext
          );
        
          const isActive = {
            fontWeight: "bold",
            backgroundColor: "lightgrey"
          };
        
          return (
            <ul className="navbar-nav mr-auto">
              <li className="nav-item">
                <NavLink className="nav-link" to="/Shop" activeStyle={isActive}>
                  Shop
                </NavLink>
              </li>
              <li className="nav-item">
                <NavLink
                  isActive={() => authorized} // <-- pass isActive callback
                  className="nav-link"
                  to="/my_profile"
                  activeStyle={isActive}
                >
                  My Profile
                </NavLink>
              </li>
            </ul>
          );
        }
        

      演示

      编辑

      只有我仍然看到一个并发症。一旦我们导航到 /dash123 已设置授权上下文,并且我的个人资料处于活动状态 - 正如我所愿。但 如果我们然后单击“商店”,商店将变为活动状态,但我的个人资料仍然存在 活跃的,它不应该是。我们可以通过改变 Shop 组件中的授权上下文。看来这个可以出 因为我们必须从每个 来自 Profile 的链接或重定向的组件。有没有 更简单的解决方案还是这只是预期的并发症?

      为此我能做的最好的事情是通过在“/my_profile”路径下嵌套用户配置文件 ID 来简化路由,并在 Nav 组件中的路径匹配上应用更多逻辑。

      1. 将配置文件路由合并为单个Route,为两个 URL 呈现。请注意,路径匹配的顺序仍然很重要,首先指定更具体的路径。

        <Route path={["/my_profile/:profile_name", "/my_profile"]}>
          <Profile />
        </Route>
        
      2. 不要将 profile 属性传递给 Profile,而是将其作为默认参数值提供。更新useEffect 逻辑以使用profile_name

        const { profile_name = "currentUser" } = useParams();
        
        ...
        
        useEffect(() => {
          if (profile_name === "currentUser") {
            setProfile(currentUser());
          } else {
            setProfile(userInfo(profile_name));
          }
        }, [profile_name, props.profile]);
        
      3. 使用useRouteMatch钩子匹配“/my_profile”路径前缀,调整isActive回调逻辑。如果与“/my_profile”完全匹配,不完全匹配,则链接应该处于活动状态用户已通过身份验证。

        const match = useRouteMatch("/my_profile");
        
        ...
        
        <NavLink
          isActive={() => match?.isExact || (match && authorized)}
          className="nav-link"
          to="/my_profile"
          activeStyle={isActive}
        >
          My Profile
        </NavLink>
        

      【讨论】:

      • 谢谢,这是我想知道的一种解决方案。只有我仍然看到一个并发症。一旦我们导航到/dash123,授权上下文就设置好了,并且我的个人资料处于活动状态——正如我所愿。但是,如果我们随后单击“商店”,商店将变为活动状态,但“我的个人资料”仍处于活动状态,而且不应该如此。我们可以通过更改 Shop 组件中的授权上下文来解决此问题。看起来这可能会失控,因为我们必须访问和更改来自 Profile 链接或重定向的每个组件的上下文。有没有更简单的解决方案,或者这只是预期的并发症?
      • @DashiellRoseBark-Huss 当然,我明白了。现在调查它。您是否有任何回旋余地来放置嵌套在“/my_profile”路由中的:profile_name 路由参数,例如“/my_profile/:profile_name”。主要问题是没有明确的方法来消除您的路线的歧义,因为它们都处于相同的深度。
      • 谢谢,虽然我确实希望路线是/:profile_name,但您的两个解决方案给了我一个想法。我将路线保存到一个数组中。我使用useRouteMatch 来检查当前路由是否是来自该阵列的非配置文件路由,并且授权是真的。这是:codesandbox.io/s/active-nav-link--i-think-it-works-ogc5p?file=/…。我认为它有效。既然您让我得到了这个答案,并且如果这看起来不错,那么如果您想将其包含在您的答案中,那么我可以给您最佳答案。否则,我会在以后自己发布,除非我发现它有问题。
      • @DashiellRoseBark-Huss 这很接近,但在导航到“/my_profile”然后返回“/”后不突出显示“profile”路线时遇到了困难。似乎您嘲笑用户个人资料/授权的方式使“授权状态”比应有的更“粘性”。我将您的想法进一步推进一两步,并定义了配置文件和非配置文件路径数组以及其他逻辑,但随后有点触及这个“授权”问题的另一面,配置文件路径仍以“bob123”突出显示。我没有时间 ATM 来处理模拟的身份验证流程,但可以在几个小时后继续。
      • @DashiellRoseBark-Huss 我仍然预见到的麻烦是如何区分配置文件路径“/my_profile”和任何身份验证状态,以及具有授权真实状态的“/:profile_name”,而不会陷入困境if-else-if-else-if 类型模式的漏洞。我需要考虑一下。
      猜你喜欢
      • 1970-01-01
      • 2014-01-08
      • 1970-01-01
      • 2021-05-24
      • 2021-03-30
      • 1970-01-01
      • 1970-01-01
      • 2013-07-20
      • 1970-01-01
      相关资源
      最近更新 更多