Skip to content

[Feature Request]: Form Groups #419

Description

@crutchcorn

Description

When building a form stepper, like so:

image

It's common for each step to have its own form. However, this complicates the form submission and validation process by requiring you to add complex logic.

Ideally, it would be nice to have a FormGroup where you could validate the group, but not the form itself - submit the value and move on to the next step.

API Proposal

// ...

const formOpts = formOptions({
    defaultValues: {
        step1: {
            name: "",
        },
        step2: {
            name: "",
        },
    },
})

const Step2Form = withForm({
    ...formOpts,
    render: function Render({ form }) {
        return (
            <form.FormGroup
                name="step2"
                onGroupSubmit={({ value: _value }) => {
                    form.handleSubmit();
                }}
            >
                {(formGroup) => (
                    <form onSubmit={(e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        formGroup.handleSubmit();
                    }}>
                        <form.AppField
                            name="step2.name"
                        >
                            {field => (
                                <field.TextField />
                            )}
                        </form.AppField>
                        <button type="submit">Next</button>
                    </form>
                )}
            </form.FormGroup>
        )
    },
})

export const StepperForm = () => {
    const [step, setStep] = useState(0);
    const form = useAppForm({
        ...formOpts,
        validationLogic: revalidateLogic(),
        validators: {
            onDynamic: z.object({
                step1: z.object({
                    name: z.string().min(2, "Name must be at least 2 characters"),
                }),
                // Will run when `step2` group is submitted or the whole form is submitted.
                // When `step2` group is submitted, it will run the form's validators, then throw aways errors on `step1`
                step2: z.object({
                    name: z.string().min(3, "Name must be at least 3 characters"),
                }),
            })
        },
        onSubmit: ({ value }) => {
            console.log("Form submitted:", value);
        }
    });

    return (
        <div>
            {step === 1 && (
                // FormGroup internally provides a sub-form context for its children including a `doNotValidate` flag to disable the parent form's validation on field changes
                <form.FormGroup
                    name="step1"
                    validators={{
                        // If `validators` are defined on the FormGroup, they will disable the parent form's validators for this group's `onGroupSubmit`
                        // Only required for async or for performance optimizations on sync validations
                        onDynamic: z.object({
                            name: z.string().min(2, "Name must be at least 2 characters"),
                        })
                    }}
                    onGroupSubmit={({ value: _value }) => {
                        setStep(step + 1);
                    }}
                    onGroupSubmitInvalid={() => { }}
                >
                    {(formGroup) => (
                        <form onSubmit={(e) => {
                            e.preventDefault();
                            e.stopPropagation();
                            formGroup.handleSubmit();
                        }}>
                            {/* Then, Field component consumes `sub-form` context and enables us to pass options to `FieldApi` */}
                            <form.AppField
                                name="step1.name"
                            >
                                {field => (
                                    <field.TextField />
                                )}
                            </form.AppField>
                            <button type="submit">Next</button>
                            {/* formGroup contains errorMaps and errors, just like forms and fields */}
                            <pre>{JSON.stringify(formGroup.state.errorMap, null, 2)}</pre>
                        </form>
                    )}
                </form.FormGroup>
            )}

            {/* Can even extract it using `formGroup` */}
            {step === 2 && (
                <Step2Form form={form} />
            )}
        </div>
    );
};

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: enhancementThis requests an enhancement to existing behaviour.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions