I have a requirement to create a search form that posts its results to a new tab/window using React.
Currently, using formik or react-hook-forms, I would leverage the native form properties to achieve this.
<Form action="my/results/page" method="POST" target="_blank" ref={formEl}>
This allows me to load the search results in a new tab once the user submits the form and validation is complete.
I would like to achieve the same functionality but using React 19's useActionState hook.
So far I have my form components and submission working minus the new tab/window requirement.
function handleFormSubmission(previousState, formData) {
newFieldValues = Object.fromEntries(formData);
const fieldsAreValid = validateFields(newFieldValues , fieldConfig);
if (fieldsAreValid) {
// Need to post newFieldValues to "my/results/page" in a new tab here
console.log('submitted', newFieldValues);
}
return newFieldValues; // Update form state for react.
}
const [state, formAction, isSubmitting] = useActionState(handleFormSubmission, initialValues);
...
return (
<form action={formAction} ref={formEl}>
{children}
</form>
);
Previously, I would just have to call event.stopPropagation() to block the submission if validation failed. Otherwise, the form would automatically post to the new target.
When using useActionState, it automatically sets the method to POST, but it also overrides the form action prop and automatically calls event.stopPropagation() upon user submission. This prevents me from leveraging the native action and target props to specify the results page & window.
So how can I submit my form data to a new target/tab within the handleFormSubmission function used by useActionState?
After spending way too long on this, I finally found a workable solution.
The HTMLFormElement:requestSubmit() method allows you to specify a submit button that overrides the parent form's submission behavior.
So to make this form open in a new window, you just need to call the requestSubmit method with a reference to a button that specifies your desired final submission behavior. That button must be of type submit and also be a child of the form you are submitting. The button can be hidden however.
The one catch I ran into is that requestSubmit did not work inside of the form's action/submission handler. I'm guessing this is because the form is already in a pending/transition state? To work around that, I put the submission request in a useEffect.
Here is the example above updated to open the results in a new tab.
const [loadReport, setLoadReport] = useState(false);
function handleFormSubmission(previousState, formData) {
newFieldValues = Object.fromEntries(formData);
const fieldsAreValid = validateFields(newFieldValues, fieldConfig);
if (fieldsAreValid) {
// Need to post newFieldValues to "my/results/page" in a new tab here
setLoadReport(true);
}
return newFieldValues; // Update form state for react.
}
const [state, formAction, isSubmitting] = useActionState(handleFormSubmission, initialValues);
useEffect(() => {
if (loadReport) {
// This button submission overrides the default form submission behavior.
formEl.current.requestSubmit(reportLoaderButton.current);
setLoadReport(false);
}
}, [loadReport, formEl, reportLoaderButton]);
...
return (
<form action={formAction} ref={formEl}>
{children}
<button
ref={reportLoaderButton}
type="submit"
formMethod="POST"
formAction={`my/results/page`}
formTarget="_blank"
style={{ display: 'none' }}
label="finalFormSubmission"
/>
</form>
);
See docs for further details.
requestSubmit: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit
useActionState: https://react.dev/reference/react/useActionState