英文:
Modal at different route in react-router v6 that uses the Navigate component
问题
这里是一个 codesandbox,用于重现这个问题。
我正在使用 react-router v6 源代码中的 modal示例,我希望在模态框中显示路由的内容。
我的路由定义如下:
<Routes location={state?.backgroundLocation || location}>
<Route path="/" element={<Layout />} />
<Route index element={<Home />} />
<Route path="gallery/:counter" element={<Gallery />} />
<Route path="*" element={<NoMatch />} />
</Route>
</Routes>
{/* 当设置了 `backgroundLocation` 时显示模态框 */}
{state?.backgroundLocation && (
<Routes>
<Route path="/modal/:counter" element={<Modal />} />
</Routes>
)}
当用户点击 Modal
链接时,我希望在模态框中显示 gallery/:counter
路由的内容,但我不确定如何触发该路由,除了使用 Navigate
组件,它会导致整个页面重定向。
英文:
Here is a codesandbox that recreates the problem.
I am using the modal example in the react-router v6 source, and I want to display the contents of a route in the modal.
I have my routes defined like this:
<Routes location={state?.backgroundLocation || location}>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="gallery/:counter" element={<Gallery />} />
<Route path="*" element={<NoMatch />} />
</Route>
</Routes>
{/* Show the modal when a `backgroundLocation` is set */}
{state?.backgroundLocation && (
<Routes>
<Route path="/modal/:counter" element={<Modal />} />
</Routes>
)}
And when the user clicks the Modal
link, I want to display the contents of the gallery/:counter
route in the modal, but I'm not sure how to trigger the route apart from using the <Navigate />
component, which causes the whole page to redirect.
答案1
得分: 1
你不能同时匹配两个不同的URL路径,因为浏览器只有一个地址栏和URL。我建议创建一个布局路由组件,将"/gallery/:counter"
路由包装起来,并有条件地将嵌套路由渲染到Modal
组件中。
示例:
const ModalLayout = () => {
const { state } = useLocation();
return state?.backgroundLocation ? (
<Modal>
<Outlet />
</Modal>
) : (
<Outlet />
);
};
Modal
组件已更新以接受一个children
属性,并将children
渲染为模态框内容。上面的Outlet
用于将嵌套路由的element
内容渲染到模态框中。
function Modal({ children }: React.PropsWithChildren) {
const navigate = useNavigate();
const buttonRef = useRef<HTMLButtonElement>(null);
function onDismiss() {
navigate(-1);
}
return (
<Dialog
aria-labelledby="label"
onDismiss={onDismiss}
initialFocusRef={buttonRef}
>
<div
style={{
display: "grid",
justifyContent: "center",
padding: "8px 8px"
}}
>
{children}
<button
style={{ display: "block" }}
ref={buttonRef}
onClick={onDismiss}
>
Close
</button>
</div>
</Dialog>
);
}
"/gallery/:counter"
路由包装在ModalLayout
布局路由中。
<Route element={<ModalLayout />}>
<Route path="gallery/:counter" element={<Gallery />} />
</Route>
为了更容易在路由组件之间共享counter
状态,并在主页和图库路由之间持续存在,建议将counter
状态从Home
组件移到另一个布局路由,通过其Outlet
上下文向后代组件提供counter
状态和设置器。
const CounterLayout = () => {
const [counter, setCounter] = useState(1);
return <Outlet context={{ counter, setCounter }} />;
};
这包装了所有需要关心counter
状态的路由,例如Home
和Gallery
组件。
<Routes>
<Route path="/" element={<Layout />}>
<Route element={<CounterLayout />}>
<Route index element={<Home />} />
<Route element={<ModalLayout />}>
<Route path="gallery/:counter" element={<Gallery />} />
</Route>
<Route path="*" element={<NoMatch />} />
</Route>
</Route>
</Routes>
英文:
You can't match two different URL paths at the same time, the browser has just the one address bar and URL. What I'd suggest is to create a layout route component that wraps the "/gallery/:counter"
route and conditionally renders the nested route into the Modal
component.
Example:
const ModalLayout = () => {
const { state } = useLocation();
return state?.backgroundLocation ? (
<Modal>
<Outlet />
</Modal>
) : (
<Outlet />
);
};
The Modal
is updated to take a children
prop and render children
as the modal content. This is where the Outlet
above is rendered so the nested routes can render their element
content into the modal.
function Modal({ children }: React.PropsWithChildren) {
const navigate = useNavigate();
const buttonRef = useRef<HTMLButtonElement>(null);
function onDismiss() {
navigate(-1);
}
return (
<Dialog
aria-labelledby="label"
onDismiss={onDismiss}
initialFocusRef={buttonRef}
>
<div
style={{
display: "grid",
justifyContent: "center",
padding: "8px 8px"
}}
>
{children}
<button
style={{ display: "block" }}
ref={buttonRef}
onClick={onDismiss}
>
Close
</button>
</div>
</Dialog>
);
}
The "/gallery/:counter"
route is wrapped in the ModalLayout
layout route.
<Route element={<ModalLayout />}>
<Route path="gallery/:counter" element={<Gallery />} />
</Route>
To make the counter
state easier to share between routed components, and also persist while switching between home and gallery routes, I suggest moving the counter
state from the Home
component into another layout route that provides counter
state and setter to descendent components via its Outlet
context.
const CounterLayout = () => {
const [counter, setCounter] = useState(1);
return <Outlet context={{ counter, setCounter }} />;
};
This wraps all the routes that want/need/care for the counter
state, e.g. the Home
and Gallery
components.
<Routes>
<Route path="/" element={<Layout />}>
<Route element={<CounterLayout />}>
<Route index element={<Home />} />
<Route element={<ModalLayout />}>
<Route path="gallery/:counter" element={<Gallery />} />
</Route>
<Route path="*" element={<NoMatch />} />
</Route>
</Route>
</Routes>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论