Skip to content

Change Form Definition

ilbrando edited this page Jun 29, 2024 · 3 revisions

On this page

Change Form Definition

Often the form definition is constant like this:

type FormFields = {
  name: string;
  age: number;
};

const fd = useFormDefinition<FormFields>({
  fields: {
    name: {
      validators: [required(), maxLength(20)]
    },
    age: {
      validators: [required(), min(3)]
    }
  }
});

But sometimes we need to change the form definition based on the field values. Lets assume we also have some fields regarding the persons employment.

type FormFields = {
  name: string;
  age: number;
  isEmployed: boolean;
  companyName: string;
  jobTitle: string;
};

When the person has employment (isEmployed === true), the fields companyName and jobTitle are required otherwise they are not. We can't just add the required validator because that would make them required when the person is not employed.

Change the validators dynamically

We want to change the validation rules when isEmployed changes. We can do this with fm.onChange which has an event handler for each field.

fm.onChange.isEmployed = value => {
  const isRequired = value ?? false; // Null is considered the same as false.
  fm.setValidators("companyName", isRequired ? [required()] : []);
  fm.setValidators("jobTitle", isRequired ? [required()] : []);
};

When isEmployed is not checked:

isEmployed is not checked

When isEmployed is checked:

isEmployed is checked

Disabling fields

In the example above the user can fill out the companyName and jobTitle fields even when isEmployed is not checked. We want to prevent this by disabling the fields.

fm.onChange.isEmployed = value => {
  const isRequired = value ?? false; // Null is considered the same as false.
  fm.setValidators("companyName", isRequired ? [required()] : []);
  fm.setValidators("jobTitle", isRequired ? [required()] : []);
+ fm.setIsDisabled("companyName", !isRequired);
+ fm.setIsDisabled("jobTitle", !isRequired);
};

Disabled fields does't participate in validation and is null in fm.values.

With this change companyName and jobTitle are disabled whenever isEmployed is changed to true. But we also need to set the initial state because otherwise they will be enabled until isEmployed is changed.

  const fd = useFormDefinition<FormFields>({
    fields: {
      name: {
        validators: [required()]
      },
      age: {
        validators: [required(), min(3)]
      },
      isEmployed: {
+       initialValue: false
      },
      companyName: {
+       initialIsDisabled: true
      },
      jobTitle: {
+       initialIsDisabled: true
      }
    }
  });

We also set the initial value of isEmployed to false. This isn't really necessary because we consider null to mean false, but it makes all the relationship between the initial values a bit more clear.

You must be aware that when you set an event handler fm.onChange.isEmployed, you are setting a property, not adding a handler. If you set it again somewhere else in your code the first value will be lost. I recommend only setting these in one place in the code and typically right after the getFormManager call.

When disabling fields you might not need to change the validators

In the above example we first implemented the validators where changed when isEmployed changed and later we added that companyName and jobTitle should be disabled when the person is not employed. As disabled fields doesn't participate in validation we can actually create the form definition with static validators and only handle disabling the fields.

const fd = useFormDefinition<FormFields>({
  fields: {
    name: {
      validators: [required(), maxLength(20)]
    },
    age: {
      validators: [required(), min(3)]
    },
    isEmployed: {
      initialValue: false
    },
    companyName: {
      initialIsDisabled: true,
      validators: [required()]
    },
    jobTitle: {
      initialIsDisabled: true,
      validators: [required()]
    }
  }
});
fm.onChange.isEmployed = value => {
  const isEnabled = value ?? false; // Null is considered the same as false.
  fm.setIsDisabled("companyName", !isEnabled);
  fm.setIsDisabled("jobTitle", !isEnabled);
};