0

I'm using Mantine uncontrolled form and I'd like to have two NumberInput components to have their disabled props changing depending on the selected value of a NativeSelect. The two inputs to be disabled and the select should be repeated elements in a list, with rows dynamically added and removed by the user; however, it doesn't seem to work even when there is just a form with data not structured in an array.

The expected behavior is that the numeric inputs should become enabled if and only if the option "Min-max enabled" is selected (empty selection implies disabled inputs).

I've wrote a complete MRE here, the following code is the component for the multi-line form. The single row example is in the SingleRowForm.tsx file and is pretty much identical, the only difference being that the initial values of the form are inside a plain object.

function MyForm() {
  const form = useForm({
    mode: "uncontrolled",
    initialValues: {
      rows: [{
        type: '',
        min: null,
        max: null
      }]
    },
    onValuesChange: (values, previous) => {
      console.log(previous);
      console.log(values);
    },
  });

  console.log('Rendering...');

  const rowFields = form.getValues().rows.map((row, idx) => {
    return (
      <Group key={idx}>
        <NativeSelect
          withAsterisk
          style={{flex: 2}}
          label="Type"
          key={form.key(`rows.${idx}.type`)}
          {...form.getInputProps(`rows.${idx}.type`)}
        >
          <option value="">---</option>
          <option value="MIN_MAX_ENABLED">Min-max enabled</option>
          <option value="MIN_MAX_DISABLED_1">Min-max disabled</option>
          <option value="MIN_MAX_DISABLED_2">Other min-max disabled</option>
        </NativeSelect>
        <NumberInput
          style={{flex: 1}}
          label="Min"
          key={form.key(`row.${idx}.min`)}
          disabled={!row.type || row.type !== "MIN_MAX_ENABLED"}
          {...form.getInputProps(`row.${idx}.min`)}
        />
        <NumberInput
          style={{flex: 1}}
          label="Max"
          key={form.key(`row.${idx}.max`)}
          disabled={!row.type || row.type !== "MIN_MAX_ENABLED"}
          {...form.getInputProps(`row.${idx}.max`)}
        />
        <Button
          onClick={() => form.removeListItem('rows', idx)}
        >
          Remove
        </Button>
      </Group>
    )
  });

  function addRowClickHandler() {
    return () => {
      form.insertListItem('rows', {
        type: '',
        min: null,
        max: null,
      })
    };
  }

  return (
    <form onSubmit={form.onSubmit((values) => console.log(values))}>
      <Fieldset legend="Rows">
        {rowFields}
        <Group justify="center" mt="md">
          <Button
            onClick={addRowClickHandler()}
          >Add row</Button>
        </Group>
      </Fieldset>
      <Group
        justify="flex-end"
        mt="md"
      >
        <Button type="submit">Submit</Button>
      </Group>
    </form>
  );
}

Running the sample, it seems that the disabled property is updated only in some cases, for instance:

  1. When adding or removing a row. To reproduce this use the following steps: Start with default selected option (inputs disabled, ok) -> Select min-max enabled (inputs become enabled, ok) -> Select min-max disabled (inputs stay enabled, not ok) -> Add row (inputs in the first row change to disabled, ok).
  2. When changing from the default options to the enabling one. A sequence of steps to reproduce this is: Start with Add row (inputs are disabled in both rows) -> Select min-max enabled in first row (inputs become enabled in the first row, ok) -> Select min-max disabled in first row (inputs stay enabled in the first row, not ok) -> Select min-max enabled in the second row (inputs in the first row become disabled and in the second row become enabled, ok).

I also tried providing a custom enhanceGetInputProps to the useForm hook to conditionally apply the disabled prop, but the result was exactly the same.

It clearly has something to do with the fact that the form is uncontrolled, my form component is not re-rendered after every value is changed, but I cannot understand why only in some cases the rendering is performed.

This use-case seems to be not covered in the Mantine docs, despite being a fair common one, or I'm missing something and could not find anything related. I could set a listener for a change using form.watch but then I'm missing how to change the disabled prop of the inputs. What approach should I follow to achieve this goal? Maybe introduce a state in my component to keep track of the disabled inputs and update the state in the form.watch? Is using a controlled form the only way to solve this, even though uncontrolled is now the recommended path to follow?

I'm reluctant to introduce state in my component since it makes the logic more complicated, just want to make sure there is no smarter way to achieve this before resorting to useState.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.