Simplified React component with form:
const itemSchema = z.object({
title: z.string().max(50),
});
type ItemFormFields = z.infer<typeof itemSchema>;
const {
register,
handleSubmit,
reset,
setError,
formState: { isSubmitting, errors }
} = useForm<ItemFormFields>({
defaultValues: {
title: "title placehnolder"
},
resolver: zodResolver(itemSchema)
});
// RTK's isLoading and error remain not updated for mutation hooks
// but the same work as expected with query hooks done through .initialize() in RRv6 loader
// const [_createItem, { isLoading, error }] = useCreateItemMutation();
const submit = useSubmit();
const actionData = useActionData<typeof itemsAction>();
const hasError = actionData && 'error' in actionData && isString(actionData.error);
useEffect(() => {
if (hasError) {
const { error } = actionData;
setError('root', {
message: error
});
}
}, [actionData, hasError, setError]);
return (
<div className="new-item">
<Form
onSubmit={(event: React.FormEvent<HTMLFormElement>) => {
handleSubmit((data: ItemFormFields) => {
submit(data, { method: 'post' });
reset();
})(event);
}}
>
<div className="new-item__form-body">
<div>
<div>
<label htmlFor="title">Title:</label>
</div>
{errors.title && <div>{errors.title.message}</div>}
</div>
<div>
<div>
<input
id="title"
type="text"
required
{...register('title')}
/>
</div>
</div>
</div>
<div className="new-item__form-footer">
<Button
disabled={isSubmitting}
type="submit"
>
{isSubmitting ? 'Loading...' : 'Submit'}
</Button>
</div>
{errors.root && <div>{errors.root.message}</div>}
</Form>
</div>
);
React Router Action function:
import { ActionFunction, ActionFunctionArgs, redirect } from 'react-router-dom';
// ...
const fromEntries = <T extends object>(data: FormData) => Object.fromEntries<string | number | File>(data) as T;
export const itemsAction = (async ({ request }: ActionFunctionArgs) => {
const data: FormData = await request.formData();
const formData: ItemFormFields = fromEntries(data);
const mutation = store.dispatch(api.endpoints.createItem.initiate(formData));
try {
const { id } = await mutation.unwrap();
return redirect(`/items/${id}`);
} catch (error) {
return {
error: 'Post-Validation error message placeholder'
};
}
}) satisfies ActionFunction;
Everything works as except for React Hook isSubmitting which remains unsubscribed and false/undefined. Same with isLoading, error from const [_createItem, { isLoading, error }] = useCreateItemMutation(); if I try with RTK instead.
The useForm data successfully gets submitted through handleSubmit and RRv6 submit - the action successfully fetches the formData with the correct Zod schema type ItemFormFields. The RTK mutation initiate successfully POSTs a new entity to the database which then returns the id in the response and the RRv6 redirect happens where it successfully fetches through a loader and everything goes smoothly... except for the isSubmitting formState in useForm that remains unsubscribed and RTK's isLoading also remains undefined. isSubmitting works as expected in a regular async example, but once it goes to the action through the void submit from useSubmit from React Router 6 - it never subscribes and never updates to true. I would assume it needs a Promise but RRv6's useSubmit provides with a regular void and does the whole flow behind the scene.
EDIT: Just regular React Router 6 (browser data router) with a route for /new:
{
path: 'new',
element: <NewItem />,
action: itemsAction
}
isSubmittingdeclared?