繁体   English   中英

如何在动态链接上有条件地将 NavLink 设置为活动

[英]How to set a NavLink to active conditionally on dynamic links

在我的菜单中,我有一个导航链接 -我的个人资料 当用户单击我的个人资料时,反应重定向到他们的个人资料。 然后导航链接按预期设置为活动,同样,应用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>

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

当用户通过地址栏导航到他们的动态配置文件路由时,如何将我的配置文件NavLink设置为活动?

例子

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

代码沙盒

代码:

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>
  );
}

问题

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

解决方案

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

  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来设置 auth state。 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以准备身份验证 state 并相应地设置NavLink活动 state。

     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> ); }

演示

编辑 how-to-set-a-navlink-to-active-on-conditionally-dynamic-links

在此处输入图像描述

编辑

只有我仍然看到一个并发症。 一旦我们导航到 /dash123,授权的上下文就设置好了,并且我的个人资料处于活动状态——正如我所愿。 但是,如果我们随后单击“商店”,商店将变为活动状态,但“我的个人资料”仍处于活动状态,而且不应该如此。 我们可以通过更改 Shop 组件中的授权上下文来解决此问题。 看起来这可能会失控,因为我们必须访问和更改来自 Profile 链接或重定向的每个组件的上下文。 有没有更简单的解决方案,或者这只是一个可以预料的并发症?

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

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

     <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>

编辑 how-to-set-a-navlink-to-active-on-conditionally-dynamic-links (forked)

我采纳了 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中设置为 true currentUsersProfile

  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' 或使用useMatchRoute的固定路线'/my_profile')

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

  const isProfileRoute = !nonProfileRoute.isExact;

密码箱

暂无
暂无

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

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