使用Remix在提交后重新加载表单中的数据。

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

Reload data in Form after submission using Remix

问题

I am using the Remix Form element to render my form containing various input fields. If there is a server-side validation error the text entered in the fields remains, which is as expected. However, when the form is successfully submitted I would like all the fields to refresh using the latest saved data from the db, which does not happen as they still contain what was previously entered.

我正在使用Remix的Form元素来渲染包含各种input字段的表单。如果出现服务器端验证错误,字段中输入的文本将保留,这是预期的行为。但是,当表单成功提交时,我希望所有字段都能使用从数据库中最新保存的数据进行刷新,但事实并非如此,它们仍然包含先前输入的内容。

A related issue is that when the form is submitted, contains password fields, and has validation issues, I would like those fields to be cleared similarly to what the standard form element does.

一个相关的问题是,当表单被提交时,包含密码字段并且存在验证问题时,我希望这些字段能够被清除,就像标准的form元素所做的那样。

Is there a way of achieving this, or am I better off switching to form? I've also tried adding reloadDocument without any luck.

有没有办法实现这一点,或者我最好切换到form?我也尝试过添加reloadDocument,但没有任何运气。

If I successfully submit this form, I would expect that the loader would be invoked again, producing an updated 10 char value. The actionData map would be null and so the input would display the abbreviated updated value. Instead, it displays the non-abbreviated value i.e. what the user entered and I have to navigate away and come back for it to properly pick it up.

如果我成功提交这个表单,我希望加载器会再次被调用,生成一个更新的10个字符的valueactionData映射将为null,因此输入字段将显示缩写的更新value。但实际情况是,它显示的是非缩写的值,即用户输入的值,我必须导航离开并返回才能正确获取它。

Code:

export async function loader({ request }: LoaderArgs) {
  const value = await getValue();
  return {
    value: value.substring(0, 10)
  }
}

export async function action({ request }: ActionArgs) {
  const formData = await request.formData()
  const newValue = formData.get("newValue")

  if (isValid(newValue)) {
    return redirect("/update-value") // same page as we are already on
  }

  return {
    errors: {
      newValue: true
    },
    values: {
      newValue
    }
  }
}

export default function Page() {
  const loaderData = useLoaderData<typeof loader>()
  const actionData = useActionData<typeof action>()

  return (
    <Form method="POST">
      <input type="text" name="newValue" id="newValue" defaultValue={actionData?.errors.newValue ? actionData?.values.newValue : loaderData.value} />
      <button type="submit"/>
    </Form>
  )
}
英文:

I am using the Remix Form element to render my form containing various input fields. If there is a server-side validation error the text entered in the fields remains, which is as expected. However, when the form is successfully submitted I would like all the fields to refresh using the latest saved data from the db, which does not happen as they still contain what was previously entered.

A related issue is that when the form is submitted, contains password fields, and has validation issues, I would like those fields to be cleared similarly to what the standard form element does.

Is there a way of achieving this, or am I better off switching to form? I've also tried adding reloadDocument without any luck.

If I successfully submit this form, I would expect that the loader would be invoked again, producing an updated 10 char value. The actionData map would be null and so the input would display the abbreviated updated value. Instead, it displays the non-abbreviated value i.e. what the user entered and I have to navigate away and come back for it to properly pick it up.

Code:

export async function loader({ request }: LoaderArgs) {
  const value = await getValue();
  return {
    value: value.substring(0, 10)
  }
}

export async function action({ request }: ActionArgs) {
  const formData = await request.formData()
  const newValue = formData.get("newValue")

  if (isValid(newValue)) {
    return redirect("/update-value") // same page as we are already on
  }

  return {
    errors: {
      newValue: true
    },
    values: {
      newValue
    }
  }
}

export default function Page() {
  const loaderData = useLoaderData<typeof loader>()
  const actionData = useActionData<typeof action>()

  return (
    <Form method="POST">
      <input type="text" name="newValue" id="newValue" defaultValue={actionData?.errors.newValue ? actionData?.values.newValue : loaderData.value} />
      <button type="submit"/>
    </Form>
  )
}

答案1

得分: 0

你遇到的问题是由React的工作方式引起的。当Remix获取数据时,无论是通过导航、表单提交还是重新验证,它只是简单地使用返回的数据更新其内部上下文,从而触发重新渲染。

Remix在导航时,包括<Outlet>在内的组件不会被卸载,这是由React处理的。由于你正在重定向到同一个页面,React只是使用更新后的数据重新渲染相同的组件。

问题在于React不会更新表单输入的defaultValue属性或useState中的初始状态。要让React使用新值,你必须告诉React你想要卸载并重新加载带有最新数据的组件。

通常,你可以使用key属性来控制这一行为。例如:

const location = useLocation()
return <Form key={location.key}>...</Form>

location.key将在每次渲染时生成一个唯一的键,因此这将确保在重定向时卸载组件。

注意:由于location.key在重新渲染时更新,这将导致表单在每次重新渲染时重新加载,而不仅仅是在导航时。这也会导致焦点丢失,所以请注意这一点。

理想情况下,你应该使用更稳定的键,只在加载器更改时才更改。这可以是实际的URL:location.pathname。由于你正在重定向到相同的路由,你可以从加载器返回一个时间戳并将其用作键。

通常,如果你重定向到与原始路由的组件树不同的不同路由,这不会成为太大的问题。只有当树相同时,你才需要处理React的工作方式。希望这能帮助澄清问题。

英文:

The issue you're having is due to how React works. When Remix fetches data, either through navigation, form post, or revalidation, it simply updates its internal context with the returned data, and that triggers a re-render.

Remix does not unmount components on navigation, including &lt;Outlet&gt;. React handles that. Since you are redirecting to the same page, React is simply re-rendering the same components with the updated data.

The issue is that React will not update the defaultValue prop on form inputs or the initial state in useState. To get React to use the new value, you must tell React you want to unmount and remount the component with the latest data.

Typically you control this with the key prop. For example:

const location = useLocation()
return &lt;Form key={location.key}&gt;...&lt;/Form&gt;

location.key will generate a unique key on each render, so this will ensure the component is unmounted when you redirect.

>NOTE: since location.key updates on re-render, this will cause the form to remount on every re-render, not just navigation. This will also cause focus to be lost, so be aware of that.

Ideally, you would use a more stable key that only changes when the loader changes. This could be the actual URL: location.pathname. Since you're redirecting to the same route, you could return a timestamp from your loader and use that as a key.

Usually, this isn't too much of an issue if you redirect to a different route where the component tree differs from your original. Only when the trees are the same, do you have to deal with how React works. I hope this helps clarify things.

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

发表评论

匿名网友

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

确定