Back to all questions

How to integrate custom input with use-form hook?

Last updated

How @mantine/form works

use-form is used to store form values, errors, touched, dirty and validation state. In order to support all @mantine/form features (including form.getInputProps()) custom inputs must accept the following props:

  • value – input value in controlled mode
  • defaultValue – input value in uncontrolled mode
  • onChange – input change handler used both for controlled and uncontrolled modes
  • error – validation error message
  • onBlur – used to set field as touched

use-uncontrolled hook

In most cases, the easiest way to add support for both controlled and uncontrolled modes is to use use-uncontrolled hook. It allows to use value and defaultValue props together and automatically handles controlled/uncontrolled mode switching.

Example of a custom input that supports both controlled and uncontrolled modes:

import { useUncontrolled } from '@mantine/hooks';

interface CustomInputProps {
  value?: string;
  defaultValue?: string;
  onChange?: (value: string) => void;
}

function CustomInput({
  value,
  defaultValue,
  onChange,
}: CustomInputProps) {
  const [_value, handleChange] = useUncontrolled({
    value,
    defaultValue,
    finalValue: 'Final',
    onChange,
  });

  return (
    <input
      type="text"
      value={_value}
      onChange={(event) => handleChange(event.currentTarget.value)}
    />
  );
}

Full example

In the following example CustomInput component supports all @mantine/form features:

  • value, defaultValue and onChange are used to control input value
  • error is used to display validation error message
  • onBlur (part of ...others props) is used to set field as touched

import { Button, Text } from '@mantine/core';
import { isNotEmpty, useForm } from '@mantine/form';
import { useUncontrolled } from '@mantine/hooks';

interface CustomInputProps extends Omit<React.ComponentPropsWithoutRef<'input'>, 'onChange'> {
  value?: string;
  defaultValue?: string;
  onChange?: (value: string) => void;
  error?: React.ReactNode;
}

function CustomInput({ value, defaultValue, onChange, error, ...others }: CustomInputProps) {
  const [_value, setValue] = useUncontrolled({
    value,
    defaultValue,
    finalValue: '',
    onChange,
  });

  return (
    <div>
      <input value={_value} onChange={(event) => setValue(event.currentTarget.value)} {...others} />
      <Text c="red">{error}</Text>
    </div>
  );
}

function Demo() {
  const form = useForm({
    mode: 'uncontrolled',
    initialValues: {
      customInput: '',
    },
    validate: {
      customInput: isNotEmpty('This field is required'),
    },
  });

  return (
    <form onSubmit={form.onSubmit(console.log)}>
      <CustomInput
        {...form.getInputProps('customInput')}
        key={form.key('customInput')}
        placeholder="Custom input"
      />

      <Button type="submit" mt="md">
        Submit
      </Button>
    </form>
  );
}