英文:
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 './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 #2 `}
<input type="text" {...register(`${dynamicId}-question2`)} />
</label>
<button type="submit">Submit</button>
</form>
</>
)
}
function App() {
return (
<>
<RouterProvider router={router}/>
</>
)
}
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-form和react-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 './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} // <---- Add a key attribute
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
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(() => {
reset(); // <-- pass any reset options, if necessary
}, [dynamicId, reset]);
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 #2 `}
<input type="text" {...register(`${dynamicId}-question2`)} />
</label>
<button type="submit">Submit</button>
</form>
</>
);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论