使用Navigate组件在React Router v6中不同路由上的模态对话框

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

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:

&lt;Routes location={state?.backgroundLocation || location}&gt;
  &lt;Route path=&quot;/&quot; element={&lt;Layout /&gt;}&gt;
    &lt;Route index element={&lt;Home /&gt;} /&gt;
    &lt;Route path=&quot;gallery/:counter&quot; element={&lt;Gallery /&gt;} /&gt;
    &lt;Route path=&quot;*&quot; element={&lt;NoMatch /&gt;} /&gt;
  &lt;/Route&gt;
&lt;/Routes&gt;

{/* Show the modal when a `backgroundLocation` is set */}
{state?.backgroundLocation &amp;&amp; (
  &lt;Routes&gt;
    &lt;Route path=&quot;/modal/:counter&quot; element={&lt;Modal /&gt;} /&gt;
  &lt;/Routes&gt;
)}

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 &lt;Navigate /&gt; 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状态的路由,例如HomeGallery组件。

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

使用Navigate组件在React Router v6中不同路由上的模态对话框

使用Navigate组件在React Router v6中不同路由上的模态对话框
使用Navigate组件在React Router v6中不同路由上的模态对话框
使用Navigate组件在React Router v6中不同路由上的模态对话框

英文:

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 &quot;/gallery/:counter&quot; route and conditionally renders the nested route into the Modal component.

Example:

const ModalLayout = () =&gt; {
  const { state } = useLocation();

  return state?.backgroundLocation ? (
    &lt;Modal&gt;
      &lt;Outlet /&gt;
    &lt;/Modal&gt;
  ) : (
    &lt;Outlet /&gt;
  );
};

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&lt;HTMLButtonElement&gt;(null);

  function onDismiss() {
    navigate(-1);
  }

  return (
    &lt;Dialog
      aria-labelledby=&quot;label&quot;
      onDismiss={onDismiss}
      initialFocusRef={buttonRef}
    &gt;
      &lt;div
        style={{
          display: &quot;grid&quot;,
          justifyContent: &quot;center&quot;,
          padding: &quot;8px 8px&quot;
        }}
      &gt;
        {children}
        &lt;button
          style={{ display: &quot;block&quot; }}
          ref={buttonRef}
          onClick={onDismiss}
        &gt;
          Close
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/Dialog&gt;
  );
}

The &quot;/gallery/:counter&quot; route is wrapped in the ModalLayout layout route.

&lt;Route element={&lt;ModalLayout /&gt;}&gt;
  &lt;Route path=&quot;gallery/:counter&quot; element={&lt;Gallery /&gt;} /&gt;
&lt;/Route&gt;

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 = () =&gt; {
  const [counter, setCounter] = useState(1);

  return &lt;Outlet context={{ counter, setCounter }} /&gt;;
};

This wraps all the routes that want/need/care for the counter state, e.g. the Home and Gallery components.

&lt;Routes&gt;
  &lt;Route path=&quot;/&quot; element={&lt;Layout /&gt;}&gt;
    &lt;Route element={&lt;CounterLayout /&gt;}&gt;
      &lt;Route index element={&lt;Home /&gt;} /&gt;
      &lt;Route element={&lt;ModalLayout /&gt;}&gt;
        &lt;Route path=&quot;gallery/:counter&quot; element={&lt;Gallery /&gt;} /&gt;
      &lt;/Route&gt;
      &lt;Route path=&quot;*&quot; element={&lt;NoMatch /&gt;} /&gt;
    &lt;/Route&gt;
  &lt;/Route&gt;
&lt;/Routes&gt;

使用Navigate组件在React Router v6中不同路由上的模态对话框

使用Navigate组件在React Router v6中不同路由上的模态对话框
使用Navigate组件在React Router v6中不同路由上的模态对话框
使用Navigate组件在React Router v6中不同路由上的模态对话框

huangapple
  • 本文由 发表于 2023年2月14日 20:33:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/75447901.html
匿名

发表评论

匿名网友

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

确定