Command Palette

Search for a command to run...

Preserving Form State

Ausath Ikram Oct 19, 2024

On my last post, we learned how to validate forms using useActionState. One problem that arises when submitting a form directly with the action attribute is that the form will reset after submission.

Sometimes we don't want that to happen. For example, say the user submits a form with an error, but not all fields are filled incorrectly, we want to keep the user's input so they don't have to retype everything, even the correct fields.

Event Handler

To preserve the form state, we need to use an event handler to call the action, we do this to utilize the event.preventDefault() method to prevent the form from resetting after submission, which is the default behavior of HTML forms.

'use client';

import { createTodo, State } from '@/lib/actions';
import { useActionState } from 'react';

export default function Form() {
  const initialState: State = {
    success: false,
    message: null,
    errors: {},
  };
  const [state, formAction, pending] = useActionState(createTodo, initialState);

  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    formAction(new FormData(event.currentTarget));
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" type="text" />
      {state.errors?.title && <p>{state.errors.title[0]}</p>}

      <input name="body" type="text" disabled={pending} />
      {state.errors?.body && <p>{state.errors.body[0]}</p>}

      <button type="submit" disabled={pending}>
        {pending ? 'Submitting...' : 'Submit'}
      </button>
      {state.message && <p>{state.message}</p>}
    </form>
  );
}

That's... basically it! now the form will not reset after submission.

Bonus: it's a good practice to wrap the formAction call with startTransition. This tells react that the update is non-urgent.

startTransition

From the official React docs:

"Transitions let you keep the user interface updates responsive even on slow devices. With a Transition, your UI stays responsive in the middle of a re-render.

For example, if the user clicks a tab but then change their mind and click another tab, they can do that without waiting for the first re-render to finish."

startTransition is particularly useful in larger applications or when dealing with slower network conditions. This will lead to smoother user experience.

Let's see how we can use it:

'use client';

import { startTransition } from 'react';
//...

export default function Form() {
  //...

  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    startTransition(() => {
      formAction(new FormData(event.currentTarget));
    });
  }

  //...
}

Without startTransition, if the network is slow, React might try to update the UI based on the pending state of formAction before it completes, potentially causing visual inconsistencies.

By wrapping the formAction call with startTransition, we tell React to wait for the action to complete before updating the UI, leading to a smoother user experience.

There's other solution to this, but I find this one to be the most straightforward.

Hope this helps!

GitHub Repo

You can find the full code for this implementation on my GitHub.

References

Blog