如何在React Router的动态路由中保持react-hook-form表单的隔离?

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

How can one keep react-hook-form forms isolated on dynamic routes in react-router?

问题

我正在构建一个使用react-hook-form来进行表单控制和react-router-dom来处理路由的React web应用程序。

问题是,我有一个包含表单的动态路由,而每个页面上的表单在逻辑上应该是隔离的,但它们却共享数据。

为了尝试解决这个问题,我创建了这个示例应用程序来说明问题:

import './App.css';
import { createBrowserRouter, NavLink, Outlet, RouterProvider, useLoaderData } from 'react-router-dom';
import { useForm } from 'react-hook-form';

const router = createBrowserRouter([
  {
    path: '/',
    element: (
      <>
        <h1>Router Issue Example</h1>
        <nav>
          <NavLink to="forms/1">Pick Form 1</NavLink>
          <span> | </span>
          <NavLink to="forms/2">Pick Form 2</NavLink>
          <span> | </span>
          <NavLink to="forms/3">Pick Form 3</NavLink>
        </nav>
        <Outlet />
      </>
    ),
    children: [
      {
        path: 'forms/:dynamicId',
        loader: DynamicFormPageLoader,
        element: <DynamicFormPage />
      }
    ]
  },
]);

function DynamicFormPageLoader({ params }: any) {
  const dynamicId = params.dynamicId;
  return { dynamicId };
}

function DynamicFormPage() {
  const { dynamicId } = useLoaderData() as { dynamicId: string };
  const { register, handleSubmit } = useForm();

  return (
    <>
      <h2>Dynamic Form Page</h2>
      <form
        onSubmit={handleSubmit((data) => {
          console.log(data);
        })}
      >
        <label>
          {`Form${dynamicId}-question #1 `}
          <input type="text" {...register(`${dynamicId}-question1`)} />
        </label>
        <label>
          {`Form${dynamicId}-question #2 `}
          <input type="text" {...register(`${dynamicId}-question2`)} />
        </label>
        <label>
          {`Form${dynamicId}-question #3 `}
          <input type="text" {...register(`${dynamicId}-question3`)} />
        </label>
        <button type="submit">Submit</button>
      </form>
    </>
  );
}

function App() {
  return (
    <>
      <RouterProvider router={router} />
    </>
  );
}

export default App;

在这个示例中,如果我转到forms/1并在表单中提交一些数据,然后转到表单2或3,它会在表单中保留那些数据,当我提交数据时,它会像一个表单一样提交。期望的行为是每个表单都是完全隔离的,因此在一个表单中提交数据只会在其他表单中记录数据。

英文:

I am building a React web application which uses react-hook-form for form control and react-router-dom for handling routing.

The issue is that I have a dynamic route which contains a form, and the forms on each page are sharing data when they should be logically isolated.

To try and solve the issue, I created this example app to illustrate the problem:

import &#39;./App.css&#39;
import { createBrowserRouter, NavLink, Outlet, RouterProvider, useLoaderData } from &#39;react-router-dom&#39;
import { useForm } from &#39;react-hook-form&#39;
const router = createBrowserRouter([
{
path: &#39;/&#39;,
element: (
&lt;&gt;
&lt;h1&gt;Router Issue Example&lt;/h1&gt;
&lt;nav&gt;
&lt;NavLink to=&quot;forms/1&quot;&gt;Pick Form 1&lt;/NavLink&gt;
&lt;span&gt;   |   &lt;/span&gt;
&lt;NavLink to=&quot;forms/2&quot;&gt;Pick Form 2&lt;/NavLink&gt;
&lt;span&gt;   |   &lt;/span&gt;
&lt;NavLink to=&quot;forms/3&quot;&gt;Pick Form 3&lt;/NavLink&gt;
&lt;/nav&gt;
&lt;Outlet /&gt;
&lt;/&gt;
),
children: [
{
path: &#39;forms/:dynamicId&#39;,
loader: DynamicFormPageLoader,
element: &lt;DynamicFormPage /&gt;
}
]
},
])
function DynamicFormPageLoader({params}: any) {
const dynamicId = params.dynamicId
return {dynamicId}
}
function DynamicFormPage() {
const {dynamicId} = useLoaderData() as {dynamicId: string};
const {register, handleSubmit} = useForm()
return (
&lt;&gt;
&lt;h2&gt;Dynamic Form Page&lt;/h2&gt;
&lt;form onSubmit={handleSubmit((data) =&gt; {
console.log(data);
})}&gt;
&lt;label&gt;
{`Form${dynamicId}-question #1 `}
&lt;input type=&quot;text&quot; {...register(`${dynamicId}-question1`)} /&gt;
&lt;/label&gt;
&lt;label&gt;
{`Form${dynamicId}-question #2 `}
&lt;input type=&quot;text&quot; {...register(`${dynamicId}-question2`)} /&gt;
&lt;/label&gt;
&lt;label&gt;
{`Form${dynamicId}-question #2 `}
&lt;input type=&quot;text&quot; {...register(`${dynamicId}-question2`)} /&gt;
&lt;/label&gt;
&lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
&lt;/form&gt;
&lt;/&gt;
)
}
function App() {
return (
&lt;&gt;
&lt;RouterProvider router={router}/&gt;
&lt;/&gt;
)
}
export default App

In this example, if I go to forms/1 and submit some data in the form and then go to form 2 or 3, it will have that data in the form and when I submit the data, it will submit it as if it was all one form. The desired behavior is for each form to be entirely isolated from one another such that submitting data in one form only logs the data in the others.

答案1

得分: 1

UPDATE: 新的解决方案已找到

我最终找出了这里的核心问题,并找到了一个更无缝的解决方案。只需在每个DynamicFormPage元素的form元素上添加一个key属性,如下所示...

import './App.css';
import { createBrowserRouter, NavLink, Outlet, RouterProvider, useLoaderData } from 'react-router-dom';
import { useForm } from 'react-hook-form';

const router = createBrowserRouter([
  {
    path: '/',
    element: (
      <>
        <h1>Router Issue Example</h1>
        <nav>
          <NavLink to="forms/1">Pick Form 1</NavLink>
          <span>   |   </span>
          <NavLink to="forms/2">Pick Form 2</NavLink>
          <span>   |   </span>
          <NavLink to="forms/3">Pick Form 3</NavLink>
        </nav>
        <Outlet />
      </>
    ),
    children: [
      {
        path: 'forms/:dynamicId',
        loader: DynamicFormPageLoader,
        element: <DynamicFormPage />
      }
    ]
  },
])

function DynamicFormPageLoader({params}: any) {
  const dynamicId = params.dynamicId
  return {dynamicId}
}

function DynamicFormPage() {
  const {dynamicId} = useLoaderData() as {dynamicId: string};
  const {register, handleSubmit} = useForm()
  
  return (
    <>
      <h2>Dynamic Form Page</h2>
      <form 
      key={dynamicId} // <---- 添加一个key属性 
      onSubmit={handleSubmit((data) => {
        console.log(data);
      })}>
        <label>
          {`Form${dynamicId}-question #1 `}
          <input type="text" {...register(`${dynamicId}-question1`)} />
        </label>
        <label>
          {`Form${dynamicId}-question #2 `}
          <input type="text" {...register(`${dynamicId}-question2`)} />
        </label>
        <label>
          {`Form${dynamicId}-question #2 `}
          <input type="text" {...register(`${dynamicId}-question2`)} />
        </label>
        <button type="submit">Submit</button>
      </form>
    </>
  )
}

function App() {
  return (
    <>
      <RouterProvider router={router}/>
    </>
  )
}

export default App

原来的问题与react-hook-formreact-router大部分无关;问题在于React根据DOM中的元素是否相同来评估它们是否“相同”。由于动态路由实际上不会更改DOM结构,React认为它们是相同的元素并保留状态。

关于根本问题的更好解释可以在Web Dev Simplified的这个视频中找到。

英文:

UPDATE: New Solution Found

I ended up rooting out the core issue here and found a more seamless solution. Just add a key attribute to the form element of each DynamicFormPage element like so...

import &#39;./App.css&#39;
import { createBrowserRouter, NavLink, Outlet, RouterProvider, useLoaderData } from &#39;react-router-dom&#39;
import { useForm } from &#39;react-hook-form&#39;
const router = createBrowserRouter([
{
path: &#39;/&#39;,
element: (
&lt;&gt;
&lt;h1&gt;Router Issue Example&lt;/h1&gt;
&lt;nav&gt;
&lt;NavLink to=&quot;forms/1&quot;&gt;Pick Form 1&lt;/NavLink&gt;
&lt;span&gt;   |   &lt;/span&gt;
&lt;NavLink to=&quot;forms/2&quot;&gt;Pick Form 2&lt;/NavLink&gt;
&lt;span&gt;   |   &lt;/span&gt;
&lt;NavLink to=&quot;forms/3&quot;&gt;Pick Form 3&lt;/NavLink&gt;
&lt;/nav&gt;
&lt;Outlet /&gt;
&lt;/&gt;
),
children: [
{
path: &#39;forms/:dynamicId&#39;,
loader: DynamicFormPageLoader,
element: &lt;DynamicFormPage /&gt;
}
]
},
])
function DynamicFormPageLoader({params}: any) {
const dynamicId = params.dynamicId
return {dynamicId}
}
function DynamicFormPage() {
const {dynamicId} = useLoaderData() as {dynamicId: string};
const {register, handleSubmit} = useForm()
return (
&lt;&gt;
&lt;h2&gt;Dynamic Form Page&lt;/h2&gt;
&lt;form 
key={dynamicId} // &lt;---- Add a key attribute 
onSubmit={handleSubmit((data) =&gt; {
console.log(data);
})}&gt;
&lt;label&gt;
{`Form${dynamicId}-question #1 `}
&lt;input type=&quot;text&quot; {...register(`${dynamicId}-question1`)} /&gt;
&lt;/label&gt;
&lt;label&gt;
{`Form${dynamicId}-question #2 `}
&lt;input type=&quot;text&quot; {...register(`${dynamicId}-question2`)} /&gt;
&lt;/label&gt;
&lt;label&gt;
{`Form${dynamicId}-question #2 `}
&lt;input type=&quot;text&quot; {...register(`${dynamicId}-question2`)} /&gt;
&lt;/label&gt;
&lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
&lt;/form&gt;
&lt;/&gt;
)
}
function App() {
return (
&lt;&gt;
&lt;RouterProvider router={router}/&gt;
&lt;/&gt;
)
}
export default App

Turns out the issue was (mostly) unrelated to both react-hook-form and react-router; it was just that React evaluates whether elements in the DOM are the "same" based on whether the same type of element is in the same spot. Because the dynamic route doesn't actually change the DOM structure, React considers them the same element and persists the state.

A better explanation of the root issue can be found in this video by Web Dev Simplified

答案2

得分: 0

你可以在dynamicId路由参数更新时重置表单状态。使用useEffect钩子来对路由参数值的更新做出反应。

function DynamicFormPage() {
  const { dynamicId } = useParams() as { dynamicId: string };
  const { handleSubmit, register, reset } = useForm();

  useEffect(() => {
    reset(); // <-- 在必要时传递任何重置选项
  }, [dynamicId, reset]);
  
  return (
    <>
      <h2>动态表单页面</h2>
      <form onSubmit={handleSubmit((data) => {
        console.log(data);
      })}>
        <label>
          {`表单${dynamicId}-问题 #1 `}
          <input type="text" {...register(`${dynamicId}-question1`)} />
        </label>
        <label>
          {`表单${dynamicId}-问题 #2 `}
          <input type="text" {...register(`${dynamicId}-question2`)} />
        </label>
        <label>
          {`表单${dynamicId}-问题 #2 `}
          <input type="text" {...register(`${dynamicId}-question2`)} />
        </label>
        <button type="submit">提交</button>
      </form>
    </>
  );
}
英文:

You could reset the form state when the dynamicId route parameter updates. Use the useEffect hook to "react" to the route param value updating.

function DynamicFormPage() {
  const { dynamicId } = useParams() as { dynamicId: string };
  const { handleSubmit, register, reset } = useForm();

  useEffect(() =&gt; {
    reset(); // &lt;-- pass any reset options, if necessary
  }, [dynamicId, reset]);
  
  return (
    &lt;&gt;
      &lt;h2&gt;Dynamic Form Page&lt;/h2&gt;
      &lt;form onSubmit={handleSubmit((data) =&gt; {
        console.log(data);
      })}&gt;
        &lt;label&gt;
          {`Form${dynamicId}-question #1 `}
          &lt;input type=&quot;text&quot; {...register(`${dynamicId}-question1`)} /&gt;
        &lt;/label&gt;
        &lt;label&gt;
          {`Form${dynamicId}-question #2 `}
          &lt;input type=&quot;text&quot; {...register(`${dynamicId}-question2`)} /&gt;
        &lt;/label&gt;
        &lt;label&gt;
          {`Form${dynamicId}-question #2 `}
          &lt;input type=&quot;text&quot; {...register(`${dynamicId}-question2`)} /&gt;
        &lt;/label&gt;
        &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
      &lt;/form&gt;
    &lt;/&gt;
  );
}

huangapple
  • 本文由 发表于 2023年6月9日 02:58:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76434941.html
匿名

发表评论

匿名网友

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

确定