[英]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”。 我是否也可以这样做,当authorized
为true
时,我的个人资料的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。
创建一个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>
使用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> ); }
在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> ); }
只有我仍然看到一个并发症。 一旦我们导航到 /dash123,授权的上下文就设置好了,并且我的个人资料处于活动状态——正如我所愿。 但是,如果我们随后单击“商店”,商店将变为活动状态,但“我的个人资料”仍处于活动状态,而且不应该如此。 我们可以通过更改 Shop 组件中的授权上下文来解决此问题。 看起来这可能会失控,因为我们必须访问和更改来自 Profile 链接或重定向的每个组件的上下文。 有没有更简单的解决方案,或者这只是一个可以预料的并发症?
我能做的最好的事情是通过在“/my_profile”路径下嵌套用户配置文件 ID 来简化路由,并在Nav
组件中的路径匹配上应用更多逻辑。
将配置文件路由合并到为两个 URL 呈现的单个Route
中。 请注意,路径匹配的顺序仍然很重要,首先指定更具体的路径。
<Route path={["/my_profile/:profile_name", "/my_profile"]}> <Profile /> </Route>
与其将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]);
使用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>
我采纳了 Drew Reese 的建议,并根据我的需要对其进行了扩展。
我做到了,如果isProfileRoute
和currentUsersProfile
满足,我的个人资料链接就处于活动状态。
<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.