Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FormStateInput Form state not persisted #466

Open
EmilEinarsen opened this issue Feb 20, 2024 · 4 comments
Open

FormStateInput Form state not persisted #466

EmilEinarsen opened this issue Feb 20, 2024 · 4 comments

Comments

@EmilEinarsen
Copy link

EmilEinarsen commented Feb 20, 2024

Describe the bug and the expected behavior

After submitting a form resulting in a server error; performing form intents when utilizing FormStateInput should persist the form state.

Conform version

v1.0.2

Steps to Reproduce the Bug or Issue

  1. With the following setup (basic fieldList):
const addressSchema = z.object({
	// ...
})

const formSchema = z.object({
	addresses: z.array(addressSchema.optional()).nonempty(),
})

export const action = async ({ request }: ActionFunctionArgs) => {
	// ...
}

function Component() {
	const actionData = useActionData<typeof action>()

	const [form, fields] = useForm({
		defaultValue: { addresses: [{}] },
		constraint: getZodConstraint(formSchema),
		lastResult: actionData?.result,
		onValidate({ formData }) {
			return parseWithZod(formData, {
				schema: formSchema,
			})
		},
		shouldRevalidate: 'onBlur',
	})

	const addresses = fields.addresses.getFieldList()

	return (
		<FormProvider context={form.context}>
			<FormStateInput />
			<Form method="POST" {...getFormProps(form)}>
				<FieldList>
					{addresses.map((props, index) => {
						const fieldset = props.getFieldset()
						return (
							<FieldList.Item
								key={props.key}
								removeButtonProps={form.remove.getButtonProps({
									name: fields.addresses.name,
									index,
								})}
							>
								<fieldset {...getFieldsetProps(props)}>

									{/* fields... */}

									<ErrorList errors={props.errors} id={props.errorId} />
								</fieldset>
							</FieldList.Item>
						)
					})}
				</FieldList>
				<FieldList.AddItem
					addButtonProps={form.insert.getButtonProps({
						name: fields.addresses.name,
					})}
				/>

				<ErrorList errors={form.errors} id={form.errorId} />

				<Button type="submit">
					Add {addresses.length > 1 ? 'addresses' : 'address'}
				</Button >
			</Form>
		</FormProvider>
	)
}
  1. Submit form and receive error action result, see error state

  2. Insert / remove fieldList item

  3. Form error state is lost

What browsers are you seeing the problem on?

No response

Screenshots or Videos

screencast-vimeo.com-2024.02.20-15_29_46.webm

Additional context

I do not think this issue is specific to v1.0.2

@edmundhung
Copy link
Owner

edmundhung commented Feb 20, 2024

This is a broader topic on whether Conform should persist server error and how.

Conform does not persists any error by default and always use the last validation result (except when you are using async validation). The <FormStateInput /> helps only by persisting the validated state to ensure that Conform will continue displaying the error if the last validation result indeed has an error. But, in your case, this particular error probably happens on form submit only and so the moment you insert a new item, it's no longer presented. I believe you can reproduce this just by triggering a field validation with the onBlur event as well.

If I understand correctly, you expect the last server validation result to be persisted until the next server validation (To be precise, server validation with no other intent). Is this correct?

@EmilEinarsen
Copy link
Author

EmilEinarsen commented Feb 20, 2024

After reading your explaination, the current behavior make sense. The server error isn't persisted since it isn't the last validation result. The client validation wouldn't give the same error, thus, no error state.

Yeah, onBlur also results in the error state being replaced. Changing shouldRevalidate to 'onSubmit' mitigates this, but insert/remove stil causes a revalidation.

Here's two thoughts:

  • Is it possible to supply validated: true to insert/remove (like update) intent in order to prevent revalidation?
    • {...form.remove.getButtonProps({ name: fields.addresses.name, index, validated: true })}
  • Would it make sense to have "server level errors" that wouldn't be replaced by validation, but only lastResult changing?

Or maybe I should just augment onValidate to take actionData.result into account to achieve this behavior?

@edmundhung
Copy link
Owner

Or maybe I should just augment onValidate to take actionData.result into account to achieve this behavior

Either this or keep the form errors in a local state seems to me the only solution for now.

const [form, fields] = useForm({
    lastResult,
    shouldRevalidate: 'onInput',
    onValidate({ formData }) {
        return parseWithZod(formData, { schema });
    },
    onSubmit() {
        setFormErrors(undefined);
    },
});
const [formErrors, setFormErrors] = useState<string[] | undefined>(
    form.errors,
);

useEffect(() => {
    if (typeof form.errors !== 'undefined') {
        setFormErrors(form.errors);
    }
}, [form.errors]);

// ...

I was trying to put up another example utilizing the async validation mechanism to persist the error but I notice another issue that's make it hard to do with form error 😅

@edmundhung
Copy link
Owner

Conform will revalidate on blur only if there was any changes made before (i.e. an input event was triggered) to minimize the chance server error get cleared simply because of moving focus out of the inputs with v1.1.0-pre.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants