@mongez/react-form
TypeScript icon, indicating that this package has built-in type declarations

3.0.16 • Public • Published

Mongez React Form

A Powerful React form handler to handle react forms regardless your desired UI.

Mongez React Form is a headless UI framework Form Handler, meaning it provides you with handlers to handle form and form controls and the UI is on your own.

This documentation will be in Typescript for better illustration.

Installation

yarn add @mongez/react-form

Or

npm i @mongez/react-form

Usage

First off, in your entry main file, we need to set the validation translations, for example:

// src/main.tsx
import {
  enValidationTranslation,
  arValidationTranslation,
} from "@mongez/react-form";
import { extend } from "@mongez/localization";

// validation object must be set with the namespace `validation`
extend("en", { validation: enValidationTranslation });
extend("ar", { validation: arValidationTranslation });

The package here has two main anchors, Form component and useFormControl hook.

Form component is the wrapper for the entire form, it will handle the form submission and data collection.

useFormControl hook is the hook that will be used to register the form control in the form, it is responsible for handling data and validation.

Example

Let's see a basic example, let's create TextInput component

// src/components/TextInput.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue } = useFormControl(props);

  return (
    <input
      type="text"
      value={value}
      onChange={(e) => {
        changeValue(e.target.value);
      }}
    />
  );
}

Here we defined our TextInput, that receives props, then we use useFormControl hook to get our form control data and register it in the form, for now we just need to get value and changeValue from the hook.

Now let's use it in our App.tsx

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  return (
    <Form
      onSubmit={{ values } => {
        console.log(values);
      }}
    >
      <TextInput name="firstName" />
      <TextInput name="lastName" />
      <button>Submit</button>
    </Form>
  );
}

The only required prop for any formControl is the name, it does not have to be unique.

Now once we click on the submit button, the onSubmit callback will be called with the form data, which is an object that contains all form controls values.

Form Controls

Any component that uses useFormControl hook will be considered as a form control, and it will be registered in the form and it will generate a formControl instance, which has the following properties:

export type FormControl = {
  /**
   * Form input name, it must be unique
   */
  name: string;
  /**
   * Form control type
   */
  type: string;
  /**
   * default value
   */
  defaultValue?: any;
  /**
   * Check if form control's value is changed
   */
  isDirty: boolean;
  /**
   * Check if form control is touched
   * Touched means that the user has focused on the input
   */
  isTouched: boolean;
  /**
   * Form input id, used as a form input flag determiner
   */
  id: string;
  /**
   * Form input value
   */
  value: any;
  /**
   * Input Initial value
   */
  initialValue: any;
  /**
   * Triggered when form starts validation
   */
  validate: () => ReactNode;
  /**
   * Set form input error
   */
  setError: (error: React.ReactNode) => void;
  /**
   * Determine if current control is visible in the browser
   */
  isVisible: () => boolean;
  /**
   * Determine whether the form input is valid, this is checked after calling the validate method
   * if the form control is not validated yet, then it will return null
   */
  isValid: boolean | null;
  /**
   * List of errors caused by rules
   */
  errorsList: {
    [rule: string]: React.ReactNode;
  };
  /**
   * Focus on the element
   */
  focus: () => void;
  /**
   * Trigger blur event on the element
   */
  blur: () => void;
  /**
   * Triggered when form resets its values
   */
  reset: () => void;
  /**
   * Form Input Error
   */
  error: React.ReactNode;
  /**
   * Unregister form control
   */
  unregister: () => void;
  /**
   * Props list to this component
   */
  props: any;
  /**
   * Check if the input's value is marked as checked
   */
  checked: boolean;
  /**
   * Set checked value
   */
  setChecked: (checked: boolean) => void;
  /**
   * Initial checked value
   */
  initialChecked: boolean;
  /**
   * Determine if form control is multiple
   */
  multiple?: boolean;
  /**
   * Collect form control value
   */
  collectValue: () => any;
  /**
   * Check if input is collectable
   */
  isCollectable: () => boolean;
  /**
   * Determine if form control is controlled
   */
  isControlled: boolean;
  /**
   * Change form control value and any other related values
   */
  change: (value: any, changeOptions?: FormControlChangeOptions) => void;
  /**
   * Determine if form control is rendered
   */
  rendered: boolean;
  /**
   * Input Ref
   */
  inputRef: any;
  /**
   * Visible element ref
   */
  visibleElementRef: any;
  /**
   * Listen when form control value is changed
   */
  onChange: (callback: (value: FormControlChange) => void) => EventSubscription;
  /**
   * Listen when form control is destroyed
   */
  onDestroy: (callback: () => void) => EventSubscription;
  /**
   * Listen to form control when value is reset
   */
  onReset: (callback: () => void) => EventSubscription;
  /**
   * Disable/Enable form control
   */
  disable: (disable: boolean) => void;
  /**
   * Determine if form control is disabled
   */
  disabled: boolean;
  /**
   * Whether unchecked value should be collected
   *
   * Works only if type is `checkbox` or `radio`
   * @default false
   */
  collectUnchecked?: boolean;
  /**
   * Define the value if control checked state is false, If collectUnchecked is true
   */
  uncheckedValue?: any;
  /**
   * Any other data to be used by the form control
   */
  data?: any;
};

Input name

The name prop is the only required prop for any form control, it is used to identify the form control in the form, and will be used to get the form control value from the form data.

The input name supports a dot notation, which means you can create a nested object using the dot notation.

Most of the time you won't need to get the input name as it is being stored internally in the form control hook, but you can get it using name property, for example:

<Form>
  <TextInput name="user.firstName" />
  <TextInput name="user.lastName" />
</Form>

The above example will generate the following form data:

{
  user: {
    firstName: "John",
    lastName: "Doe"
  }
}

You may use user[name] notation instead of user.name notation, it will be converted into user.name but it is not recommended to use it.

Input type

Input type is also required when passing props to the form control hook, for example:

// src/components/TextInput.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";

export default function TextInput({ type = "text", props }: FormControlProps) {
  const { value, changeValue } = useFormControl(props);

  return (
    <input
      value={value}
      onChange={(e) => {
        changeValue(e.target.value);
      }}
    />
  );
}

The type will be passed to the form control, if not defined it will be set to text by default.

Controlled and Uncontrolled input values

You can pass value and onChange props to any form control, which means you can control the form control value from outside the form control, for example:

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const [value, changeValue] = useState("");

  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput
        name="firstName"
        value={value}
        onChange={(value) => {
          changeValue(value);
        }}
      />
      <button>Submit</button>
    </Form>
  );
}

This will allow you control the input value from outside the form control, if you notice the onChange prop receives a direct value instead of an event object, this is because the form control will handle the event object and pass the value to the onChange prop.

You can also pass defaultValue prop to any form control, which means you can set the initial value of the form control, for example:

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="firstName" defaultValue="John" />
      <button>Submit</button>
    </Form>
  );
}

Any form control is controlled internally, meaning that you'll always receive a value property from the useFormControl hook regardless of the input type, and you can change the value using the changeValue function.

Getting event and other options

onChange as mentioned, dispatches the value directly, but you can also manage any other data that you receive from the onChange prop, for example:

// src/components/TextInput.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue } = useFormControl(props);

  return (
    <input
      value={value}
      onChange={(e) => {
        changeValue(e.target.value, {
          event: e,
          otherOption: "some value",
        });
      }}
    />
  );
}

The changeValue function accepts a second argument which is an object that will be passed to the onChange prop, for example:

Now you can receive the event and other options in the onChange prop in the second argument, for example:

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const [value, changeValue] = useState("");

  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput
        name="firstName"
        value={value}
        onChange={(value: string, options) => {
          changeValue(value);
          console.log(options.event); // that property we defined in the TextInput component
        }}
      />
      <button>Submit</button>
    </Form>
  );
}

Checkbox inputs

Any form control labeled with type equal to checkbox will have a slight difference in the onChange prop, for example:

// src/components/Checkbox.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";

export default function Checkbox(props: FormControlProps) {
  const { checked, setChecked } = useFormControl({
    ...props,
    type: "checkbox", // must be explicitly set to checkbox
  });

  return (
    <input
      type="checkbox"
      checked={checked}
      onChange={(e) => {
        setChecked(e.target.checked);
      }}
    />
  );
}

The setChecked function accepts a boolean value, which means you can pass the checked property of the event object to the setChecked function.

You can now use the Checkbox component in the form, for example:

// src/App.tsx
import { Form } from "@mongez/react-form";
import Checkbox from "./components/Checkbox";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <Checkbox defaultChecked={true} name="rememberMe" />
      <button>Submit</button>
    </Form>
  );
}

Now if we want to control the check state, we can pass the checked and onChange props to the Checkbox component, for example:

// src/App.tsx
import { Form } from "@mongez/react-form";
import Checkbox from "./components/Checkbox";

export default function App() {
  const [checked, setChecked] = useState(false);

  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <Checkbox
        checked={checked}
        onChange={(checked) => {
          setChecked(checked);
        }}
        name="rememberMe"
      />
      <button>Submit</button>
    </Form>
  );
}

Here, the checked state is sent as the first argument, if you want to get the value, extract it from the second argument, for example:

// src/App.tsx
import { Form } from "@mongez/react-form";
import Checkbox from "./components/Checkbox";

export default function App() {
  const [checked, setChecked] = useState(false);

  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <Checkbox
        checked={checked}
        onChange={(checked, { value }) => {
          setChecked(checked);
          console.log(value); // 1
        }}
        name="rememberMe"
      />
      <button>Submit</button>
    </Form>
  );
}

You can of course assign the value if the component is checked, for example:

// src/components/Checkbox.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";

export default function Checkbox(props: FormControlProps) {
  const { checked, setChecked } = useFormControl(props);

  return (
    <input
      type="checkbox"
      checked={value}
      onChange={(e) => {
        setChecked(e.target.checked);
      }}
    />
  );
}

You can also set the unchecked value as well by passing it to useFormControl in the second argument object.

// src/components/Checkbox.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";

export default function Checkbox(props: FormControlProps) {
  const { checked, setChecked } = useFormControl(props, {
    uncheckedValue: 0,
  });

  return (
    <input
      type="checkbox"
      checked={value}
      onChange={(e) => {
        setChecked(e.target.checked);
      }}
    />
  );
}

Form Control Id

Each form control must have a unique id, if there is no id passed in the props list, the form control hook will generate a unique id and return it, for example:

// src/components/TextInput.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue, id } = useFormControl(props);

  return (
    <input
      type="text"
      value={value}
      id={id}
      onChange={(e) => {
        changeValue(e.target.value);
      }}
    />
  );
}

In V3, the id will be by default ${name}-input to give better accessibility, but you can still pass the id to the form control.

useRadioInput

Added in V3.0.0

As radio input is some sort of selection but with variant values for each radio input, useRadioInput will make it easier to control a single form control with multiple values from variant radio inputs.

First step is to create a RadioGroup component:

import { requiredRule,  RadioGroupContext, type FormControlProps } from "@mongez/react-form";

type RadioGroupProps = FormControlProps & {
  children: React.ReactNode;
};

export default function RadioGroup(props\: RadioGroupProps) {
  const {value, changeValue, error} = useFormControl({
    ...props,
    rules: [requiredRule],
  });

  return (
    <RadioGroupContext.Provider value={{
      value,
      changeValue
    }}>
      {children}
    </RadioGroupContext.Provider>
  );
}

So what we did here is we used the RadioGroupContext to wrap our radio inputs, then we passed the value and changeValue to the context provider.

Now let's define our RadioInput Component:

import { useRadioInput } from "@mongez/react-form";

export default function RadioInput({
  value,
  children,
}: {
  value: any;
  children: React.ReactNode;
}) {
  const { isSelected, changeValue } = useRadioInput(value);

  return (
    <label>
      <input type="radio" checked={isSelected} onChange={changeValue} />
      {children}
    </label>
  );
}

Now we can use the RadioGroup and RadioInput components in our form:

import { Form } from "@mongez/react-form";
import RadioGroup from "./components/RadioGroup";
import RadioInput from "./components/RadioInput";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <RadioGroup name="gender">
        <RadioInput value="male">Male</RadioInput>
        <RadioInput value="female">Female</RadioInput>
      </RadioGroup>
    </Form>
  );

We can mark it as a required field by passing the required prop to the RadioGroup component.

Input Ref

Passing inputRef to the input that we're working on is important for handling the input focus, blur and so on

// src/components/TextInput.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";
import { useEffect } from "react";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue, id, inputRef, formControl } =
    useFormControl(props);

  useEffect(() => {
    setTimeout(() => {
      // focus the input after 1 second
      // this requires the inputRef to be passed to the input
      formControl.focus();
    }, 1000);
  }, []);

  return (
    <input
      type="text"
      value={value}
      id={id}
      ref={inputRef}
      onChange={(e) => {
        changeValue(e.target.value);
      }}
    />
  );
}

You can also perform blur as well:

// src/components/TextInput.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";
import { useEffect } from "react";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue, id, inputRef, formControl } =
    useFormControl(props);

  useEffect(() => {
    setTimeout(() => {
      // focus the input after 1 second
      // this requires the inputRef to be passed to the input
      formControl.focus();

      setTimeout(() => {
        // blur the input after focusing on it with 1 second
        formControl.blur();
      }, 1000);
    }, 1000);
  }, []);

  return (
    <input
      type="text"
      value={value}
      id={id}
      ref={inputRef}
      onChange={(e) => {
        changeValue(e.target.value);
      }}
    />
  );
}

Disabled Prop

Form control also preserves the disabled prop and return it directly, for example:

// src/components/TextInput.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue, id, disabled } = useFormControl(props);

  return (
    <input
      type="text"
      value={value}
      id={id}
      disabled={disabled}
      onChange={(e) => {
        changeValue(e.target.value);
      }}
    />
  );
}

If you want to change the state of disable state, you can use disable and enable methods, for example:

// src/components/TextInput.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue, id, disabled, disable, formControl } =
    useFormControl(props);

  useEffect(() => {
    setTimeout(() => {
      // disable the input after 1 second
      disable();
      // or using the formControl
      formControl.disable();
    }, 1000);
  }, []);

  return (
    <input
      type="text"
      value={value}
      id={id}
      disabled={disabled}
      onChange={(e) => {
        changeValue(e.target.value);
      }}
    />
  );
}

Is Touched

Added in v2.1.0

Is touched in terms of form control concept means that the user has focused on the input.

You can check if the form control is touched or not using formControl.isTouched property, for example:

// src/components/TextInput.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue, id, disabled, disable, formControl } =
    useFormControl(props);

  useEffect(() => {
    setTimeout(() => {
      // check if the input is touched
      if (formControl.isTouched) {
        // do something
      }
    }, 1000);
  }, []);

  return (
    <input
      type="text"
      value={value}
      id={id}
      disabled={disabled}
      onChange={(e) => {
        changeValue(e.target.value);
      }}
    />
  );
}

Is Dirty

Added in v2.1.0

Is dirty in terms of form control concept means that the form control value is changed.

You can check if the form control is dirty or not using formControl.isDirty property, for example:

// src/components/TextInput.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue, id, disabled, disable, formControl } =
    useFormControl(props);

  useEffect(() => {
    setTimeout(() => {
      // check if the input is dirty
      if (formControl.isDirty) {
        // do something
      }
    }, 1000);
  }, []);

  return (
    <input
      type="text"
      value={value}
      id={id}
      disabled={disabled}
      onChange={(e) => {
        changeValue(e.target.value);
      }}
    />
  );
}

Getting other props

Apart from the previous props, any other prop will be sent to the input will be returned as otherProps, for example:

// src/components/Checkbox.tsx
import { useFormControl, FormControlProps } from "@mongez/react-form";

export default function Checkbox(props: FormControlProps) {
  const { checked, setChecked, otherProps } = useFormControl(props);

  return (
    <input
      type="checkbox"
      checked={value}
      onChange={(e) => {
        setChecked(e.target.checked);
      }}
      {...otherProps}
    />
  );
}

Input Validation

Now let's move to the validation part, we can split it into two parts, using rules or using manual validation.

Using rules

First off, let's define the rules list that could be used for TextInput component, for example:

// src/components/TextInput.tsx
import { Form, requiredRule } from "@mongez/react-form";

export default function TextInput({
  rules = [requiredRule],
  ...props
}: FormControlProps) {
  const { value, changeValue } = useFormControl({
    ...props,
    rules,
  });

  return (
    <input
      type="text"
      value={value}
      onChange={(e) => {
        changeValue(e.target.value);
      }}
    />
  );
}

Here we defined the default rules that could run against the value change, now if we want to use it, we just have to pass required prop to the TextInput component, for example:

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
    >
      <TextInput name="name" required />
      <button>Submit</button>
    </Form>
  );
}

Now if we submitted the form, it won't go to onSubmit method, because the name input is required, and it's empty.

Displaying the error

If the rule is not valid, then it will return the error message, so we can display it in the UI, for example:

// src/components/TextInput.tsx
import { Form, requiredRule } from "@mongez/react-form";

export default function TextInput({
  rules = [requiredRule],
  ...props
}: FormControlProps) {
  const { value, changeValue, error } = useFormControl({
    ...props,
    rules,
  });

  return (
    <>
      <input
        type="text"
        value={value}
        onChange={(e) => {
          changeValue(e.target.value);
        }}
      />
      {error && (
        <span
          style={{
            color: "red",
          }}
        >
          {error}
        </span>
      )}
    </>
  );
}

The error will appear based on current locale code from Mongez Localization

For now translation supports Six languages, English, Arabic, French, Spanish, Italian and Germany with locale codes en, ar, fr, es, it and de respectively.

Let's add another rule minLengthRule to the TextInput component, for example:

// src/components/TextInput.tsx
import { Form, requiredRule, minLengthRule } from "@mongez/react-form";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue, error } = useFormControl({
    rules: [requiredRule, minLengthRule],
    ...props,
  });

  return (
    <>
      <input
        type="text"
        value={value}
        onChange={(e) => {
          changeValue(e.target.value);
        }}
      />
      {error && (
        <span
          style={{
            color: "red",
          }}
        >
          {error}
        </span>
      )}
    </>
  );
}

Now to make the minLengthRule work, the TextInput component must receive minLength prop, for example:

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
    >
      <TextInput name="name" required minLength={3} />
      <button>Submit</button>
    </Form>
  );
}

Rules list

Here are the available rules that you can use:

  • requiredRule: Check if the value is not empty.
    • null, undefined, '' and [] are considered empty.
    • Requires required prop to be present.
    • Translation Key: validation.required.
  • minLengthRule: Check if the value's length is greater than or equal to the minLength prop.
    • Requires minLength prop to be present.
    • Translation Key: validation.minLength, receives :length as a placeholder.
    • minLength prop will be preserved from being passed to otherProps.
    • Works with strings and arrays.
  • maxLengthRule: Check if the value's length is less than or equal to the maxLength prop.
    • Requires maxLength prop to be present.
    • Translation Key: validation.maxLength, receives :length as a placeholder.
    • maxLength prop will be preserved from being passed to otherProps.
    • Works with strings and arrays.
  • lengthRule: Check if the value's length is equal to the length prop.
    • Requires length prop to be present.
    • Translation Key: validation.length, receives :length as a placeholder.
    • length prop will be preserved from being passed to otherProps.
    • Works with strings and arrays.
  • minRule: Check if the value is greater than or equal to the min prop.
    • Requires min prop to be present.
    • Translation Key: validation.min, receives :min as a placeholder.
    • min prop will be preserved from being passed to otherProps.
    • Works with numbers.
  • maxRule: Check if the value is less than or equal to the max prop.
    • Requires max prop to be present.
    • Translation Key: validation.max, receives :max as a placeholder.
    • max prop will be preserved from being passed to otherProps.
    • Works with numbers.
  • emailRule: Check if the value is a valid email.
    • Translation Key: validation.email.
    • Requires type prop to be email.
  • numberRule: Check if the value is a valid number.
    • Translation Key: validation.number.
    • Requires type prop to be number.
  • floatRule: Check if the value is a valid float number.
    • Translation Key: validation.float.
    • Requires type prop to be float.
  • integerRule: Check if the value is a valid integer number.
    • Translation Key: validation.integer.
    • Requires type prop to be integer.
  • patternRule: Check if the value matches the pattern prop.
    • Requires pattern prop to be present.
    • Translation Key: validation.pattern, receives :pattern as a placeholder.
    • pattern prop will be preserved from being passed to otherProps.
  • alphabetRule: Check if the value is a valid alphabet.
    • Translation Key: validation.alphabet.
    • Requires type prop to be alphabet.
  • matchRule: Check if the value matches the value of the input with the name of the match prop.
    • Requires match prop to be present.
    • Translation Key: validation.match, receives :matchingInput as a placeholder.
    • match prop will be preserved from being passed to otherProps.
    • url type is also supported, you must set the input type to url to make it work and add urlRule as well.

Example of usage for each rule:

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
    >
      <TextInput name="name" required />
      <TextInput name="email" type="email" required />
      <TextInput name="age" type="number" required />
      <TextInput name="salary" type="float" required />
      <TextInput name="phone" type="integer" required />
      <TextInput name="password" type="password" required />
      <TextInput name="confirmPassword" type="password" required match="password" />
      <TextInput name="website" type="url" required />
      <TextInput name="address" type="text" required minLength={10} maxLength={100} />
      <TextInput name="zipCode" type="text" required length={5} />
      <TextInput name="phone" type="text" required pattern={/^01[0-2|5]{1}[0-9]{8}$/} />
      <TextInput name="name" required alphabet />
      <button>Submit</button>
    </Form>
  );
}
// src/components/TextInput.tsx
import {
  Form,
  requiredRule,
  minLengthRule,
  maxLengthRule,
  lengthRule,
  emailRule,
  numberRule,
  floatRule,
  integerRule,
  patternRule,
  alphabetRule,
  matchRule,
} from "@mongez/react-form";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue, error } = useFormControl({
    rules: [
      requiredRule,
      minLengthRule,
      maxLengthRule,
      lengthRule,
      emailRule,
      numberRule,
      floatRule,
      integerRule,
      patternRule,
      alphabetRule,
      matchRule,
    ],
    ...props,
  });

  return (
    <>
      <input
        type="text"
        value={value}
        onChange={(e) => {
          changeValue(e.target.value);
        }}
      />
      {error && (
        <span
          style={{
            color: "red",
          }}
        >
          {error}
        </span>
      )}
    </>
  );
}

This is just a demo, please make a component for each type separately, for example EmailInput, NumberInput, FloatInput, IntegerInput, PasswordInput, UrlInput, AlphabetInput and so on.

Create custom rule

You can of course create a custom rule to use it among your inputs, for example:

// src/validation/phoneNumber.ts
import { type InputRule } from "@mongez/react-form";
import { trans } from "@mongez/localization";

export const phoneNumberRule: InputRule = {
  name: "phoneNumber",
  requiresType: "number",
  validate: ({ value, type }) => {
  const regex = /^01[0-2|5]{1}[0-9]{8}$/;

  if (!regex.test(value)) {
    return trans("validation.phoneNumber");
  }
};

Here is the InputRule interface:

export type InputRule = {
  validate: (
    options: InputRuleOptions
  ) => InputRuleResult | Promise<InputRuleResult>;
  /**
   * Validation rule name
   */
  name?: string;
  /**
   * Preserved props will be used to prevent these props to be passed to `otherProps` object
   */
  preservedProps?: string[];
  /**
   * Whether it requires a value to be called or not
   *
   * @default true
   */
  requiresValue?: boolean;
  /**
   * Determine what input type to run this input against
   */
  requiresType?: string;
  /**
   * Called when form control is initialized
   */
  onInit?: (options: InitOptions) => EventSubscription | undefined;
};

Now you can use it in your TextInput component

// src/components/TextInput.tsx
import { Form, requiredRule } from "@mongez/react-form";
import { phoneNumberRule } from "../validation/phoneNumber";

export default function TextInput({
  rules = [requiredRule, phoneNumberRule],
  ...props
}: FormControlProps) {
  const { value, changeValue, type, error } = useFormControl({
    ...props,
    rules,
  });

  return (
    <>
      <input
        type={type}
        value={value}
        onChange={(e) => {
          changeValue(e.target.value);
        }}
      />
      {error && (
        <span
          style={{
            color: "red",
          }}
        >
          {error}
        </span>
      )}
    </>
  );
}

And that's it!

Now for usage, you can use it like this:

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="phone" type="phoneNumber" required />
      <button>Submit</button>
    </Form>
  );
}

Single Component Validation

Sometimes, you may need to apply a certain validation only on a certain component call, this where you can use validate prop for that purpose.

// src/App.tsx
import TextInput from "./components/TextInput";
import { useState } from "react";

export default function App() {
  const validateUsername = ({ value }) => {
    if (!value) return; // skip the validation if the value is empty

    const usernameRegex = /^[a-zA-Z0-9]+$/;

    if (!usernameRegex.test(value)) {
      return "Username must be alphanumeric";
    }
  };

  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" validate={validateUsername} />
      <button>Submit</button>
    </Form>
  );
}

You can also async the validation.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";
import { useState } from "react";
import { checkUsername } from "./api";

export default function App() {
  const [isCheckingUsername, setIsCheckingUsername] = useState(false);

  const validateUsername = async ({ value }) => {
    if (!value) return; // skip the validation if the value is empty

    // check for username from api
    setIsCheckingUsername(true);

    try {
      await checkUsername(value);
    } catch (error) {
      return error.message;
    } finally {
      setIsCheckingUsername(false);
    }
  };

  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" validate={validateUsername} />
      <button>Submit</button>
    </Form>
  );
}

This will stop any other validator from being called until the validateUsername function is resolved.

Customizing the error message

There are multiple ways to override the error message:

  1. Overriding the translation errors.
  2. Changing the error keys per component call.
  3. Override rule error per component call.
Overriding the translation errors

You can override the translation errors from the translation list using groupedTranslations method from Mongez Localization, here is the current error messages list.

// src/locales.ts
import { groupedTranslations } from "@mongez/localization";

export const validationTranslation = {
  required: {
    en: "This input is required",
    ar: "هذا الحقل مطلوب",
    fr: "Ce champ est requis",
    es: "Este campo es obligatorio",
    it: "Questo campo è obbligatorio",
    de: "Dieses Feld ist erforderlich",
  },
  invalidEmailAddress: {
    en: "Invalid Email Address",
    ar: "بريد الكتروني خاطئ",
    fr: "Adresse e-mail invalide",
    es: "Dirección de correo electrónico no válida",
    it: "Indirizzo email non valido",
    de: "Ungültige E-Mail-Adresse",
  },
  url: {
    en: "Invalid URL",
    ar: "رابط غير صحيح",
    fr: "URL invalide",
    es: "URL no válida",
    it: "URL non valido",
    de: "Ungültige URL",
  },
  min: {
    en: "Value can not be lower than :min",
    ar: "القيمة يجب أن لا تقل عن :min",
    fr: "La valeur ne peut pas être inférieure à :min",
    es: "El valor no puede ser inferior a :min",
    it: "Il valore non può essere inferiore a :min",
    de: "Der Wert darf nicht kleiner sein als :min",
  },
  max: {
    en: "Value can not be greater than :max",
    ar: "القيمة يجب أن لا تزيد عن :max",
    fr: "La valeur ne peut pas être supérieure à :max",
    es: "El valor no puede ser superior a :max",
    it: "Il valore non può essere superiore a :max",
    de: "Der Wert darf nicht größer sein als :max",
  },
  matchElement: {
    en: "This input is not matching with :matchingInput",
    ar: "هذا الحقل غير متطابق مع :matchingInput",
    fr: "Ce champ ne correspond pas à :matchingInput",
    es: "Este campo no coincide con :matchingInput",
    it: "Questo campo non corrisponde a :matchingInput",
    de: "Dieses Feld stimmt nicht mit :matchingInput überein",
  },
  length: {
    en: "This input should have :length characters",
    ar: "حروف الحقل يجب ان تساوي :length",
    fr: "Ce champ doit avoir :length caractères",
    es: "Este campo debe tener :length caracteres",
    it: "Questo campo deve avere :length caratteri",
    de: "Dieses Feld sollte :length Zeichen haben",
  },
  minLength: {
    en: "This input can not be less than :length characters",
    ar: "هذا الحقل يجب ألا يقل عن :length حرف",
    fr: "Ce champ ne peut pas être inférieur à :length caractères",
    es: "Este campo no puede ser inferior a :length caracteres",
    it: "Questo campo non può essere inferiore a :length caratteri",
    de: "Dieses Feld darf nicht weniger als :length Zeichen haben",
  },
  maxLength: {
    en: "This input can not be greater than :length characters",
    ar: "هذا الحقل يجب ألا يزيد عن :length حرف",
    fr: "Ce champ ne peut pas être supérieur à :length caractères",
    es: "Este campo no puede ser superior a :length caracteres",
    it: "Questo campo non può essere superiore a :length caratteri",
    de: "Dieses Feld darf nicht mehr als :length Zeichen haben",
  },
  pattern: {
    en: "This input is not matching with the :pattern",
    ar: "هذا الحقل غير مطابق :pattern",
    fr: "Ce champ ne correspond pas au :pattern",
    es: "Este campo no coincide con el :pattern",
    it: "Questo campo non corrisponde al :pattern",
    de: "Dieses Feld stimmt nicht mit dem :pattern überein",
  },
  number: {
    en: "This input accepts only numbers",
    ar: "هذا الحقل لا يقبل غير أرقام فقط",
    fr: "Ce champ ne peut contenir que des chiffres",
    es: "Este campo solo acepta números",
    it: "Questo campo accetta solo numeri",
    de: "Dieses Feld akzeptiert nur Zahlen",
  },
  integer: {
    en: "This input accepts only integer digits",
    ar: "هذا الحقل لا يقبل غير أرقام صحيحة",
    fr: "Ce champ ne peut contenir que des chiffres entiers",
    es: "Este campo solo acepta dígitos enteros",
    it: "Questo campo accetta solo cifre intere",
    de: "Dieses Feld akzeptiert nur ganze Zahlen",
  },
  float: {
    en: "This input accepts only integer or float digits",
    ar: "هذا الحقل لا يقبل غير أرقام صحيحة او عشرية",
    fr: "Ce champ ne peut contenir que des chiffres entiers ou décimaux",
    es: "Este campo solo acepta dígitos enteros o decimales",
    it: "Questo campo accetta solo cifre intere o decimali",
    de: "Dieses Feld akzeptiert nur ganze oder Dezimalzahlen",
  },
  alphabet: {
    en: "This input accepts only alphabets",
    ar: "هذا الحقل لا يقبل غير أحرف فقط",
    fr: "Ce champ ne peut contenir que des lettres",
    es: "Este campo solo acepta letras",
    it: "Questo campo accetta solo lettere",
    de: "Dieses Feld akzeptiert nur Buchstaben",
  },
};

groupedTranslations("validation", validationTranslation);

Changing the error keys per component call

This coulld be useful for some rules such as thematch rule to override the error key with the matching input name.

// srcc/App.tsx
import { Form } from "@mongez/form";
import { TextInput } from "@mongez/form";

export default function App() {
  return (
    <Form>
      <TextInput name="password" type="password" required minLength={8} />
      <TextInput
        name="confirmPassword"
        match="password"
        type="password"
        errorKeys={{
          matchingInput: "Passowrd Input",
        }}
      />
    </Form>
  );
}

If the passowrd input does not match the confirm password input, the error message will be:

This input is not matching with Passowrd Input

If you installed Localization React package, yoou can get benefit from passing jsx element instead of just plain text.

// srcc/App.tsx
import { Form } from "@mongez/form";
import { TextInput } from "@mongez/form";

export default function App() {
  return (
    <Form>
      <TextInput name="password" type="password" required minLength={8} />
      <TextInput
        name="confirmPassword"
        match="password"
        type="password"
        errorKeys={{
          matchingInput: <span className="text-danger">Passowrd Input</span>,
        }}
      />
    </Form>
  );
}

Changing the error message per component call

You can also change the entire error message, forr example when working withe pattern rule, you can pass the pattern prop as a RegExp object, and then pass the errorMessages prop to override the error message.

// srcc/App.tsx
import { Form } from "@mongez/form";
import { TextInput } from "@mongez/form";

export default function App() {
  return (
    <Form>
      <TextInput
        name="username"
        placeholder="Username must accept only letters and numbers"
        pattern={/^[a-zA-Z0-9]+$/}}
        errorMessages={{
          pattern: "Username must accept only letters and numbers"
        }}
      />
    </Form>
  );
}

It's recommended to use trans function if you're web application has multiple languages.

// srcc/App.tsx
import { Form } from "@mongez/form";
import { trans } from "@mongez/localization";
import { TextInput } from "@mongez/form";

export default function App() {
  return (
    <Form>
      <TextInput
        name="username"
        placeholder="Username must accept only letters and numbers"
        pattern={/^[a-zA-Z0-9]+$/}}
        errorMessages={{
          pattern: trans('usernamePatternError')
        }}
      />
    </Form>
  );
}

Validate all Rules

Added in v2.2.0

By default validation rules are executed one by one, if one of them is not valid, the validation process will stop and the error message will be displayed.

To override this, pass to the second argument of useFormControl hook an object with validateAll property set to true.

// src/components/TextInput.tsx
import { Form, requiredRule, minLengthRule, type FormControlProps } from "@mongez/react-form";

export default function TextInput(props:FormControlProps) {
  const { value, changeValue, error } = useFormControl({
    rules: [requiredRule, minLengthRule],
    ...props,
  }, { validateAll: true });

  return (
    <>
      <input
        type="text"
        value={value}
        onChange={(e) => {
          changeValue(e.target.value);
        }}
      />
      {error && (
        <span
          style={{
            color: "red",
          }}
        >
          {error.map((error, index) => (
            <div key={index}>{error}</div>
          )}
        </span>
      )}
    </>
  );
}

In this case the error property will be an array of error messages.

Get errors list based on rule

Added in v2.2.0

If you want to detect what rules made the validation fail, you can use errorsList property from the formControl object.

// src/components/TextInput.tsx
import {
  Form,
  requiredRule,
  minLengthRule,
  type FormControlProps,
} from "@mongez/react-form";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue, errorsList, error, formControl } = useFormControl(
    {
      rules: [requiredRule, minLengthRule],
      ...props,
    }
  );

  return (
    <>
      <input
        type="text"
        value={value}
        onChange={(e) => {
          changeValue(e.target.value);
        }}
      />
      {error && (
        <span
          style={{
            color: "red",
          }}
        >
          {error}
        </span>
      )}

      {errorsList.minLength && (
        <span
          style={{
            color: "red",
            fontSize: "16px",
            fontWeight: "bold",
          }}
        >
          {errorsList.minLength}
        </span>
      )}
    </>
  );
}

Form Submission

The onSubmitprop is the only required prop for Form component, also, it will not be called until all form controls are valid.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";
import { useState } from "react";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <button>Submit</button>
    </Form>
  );
}

If the form is not submitted programatically, you can gett event object from the onSubmit callback

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const submitForm = ({ values, event }) => {
    const formElement = event.target;
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <button>Submit</button>
    </Form>
  );
}

Don't use event.preventDefault() in the onSubmit callback, it will be called automatically.

Getting form values

You can get the form values from the onSubmit callback using the values property.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <button>Submit</button>
    </Form>
  );
}

However, if you want to get it as FormData, use formData property instead.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";
import createAccount from "./services/createAccount";

export default function App() {
  const submitForm = ({ formData }) => {
    createAccount(formData).then((response) => {
      //...
    });
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <button>Submit</button>
    </Form>
  );
}

This is useful if you're working with multipart/form-data requests and want to send some files.

Ignoring Empty Values

By default, the form will collect all form controls with its values regardlress of their values, but if you want to ignore empty values, you can pass ignoreEmptyValues prop to the Form component.

Without using ignoreEmptyValues prop:

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const submitForm = ({ values }) => {
    // if the username input is empty, it will be included in the values object with an empty string
    console.log(values); // { name: "John Doe", username: "" }
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <button>Submit</button>
    </Form>
  );
}
// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const submitForm = ({ values }) => {
    // if the username input is empty, it will not be included in the values object
    console.log(values); // { name: "John Doe" }
  };

  return (
    <Form onSubmit={submitForm} ignoreEmptyValues>
      <TextInput name="name" required />
      <TextInput name="username" />
      <button>Submit</button>
    </Form>
  );
}

Getting form instance

The last thing that you may receive from the onSubmit callback is the form instance, which is an object that implements FormInterface.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const submitForm = ({ form }) => {
    console.log(form);
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <button>Submit</button>
    </Form>
  );
}

You can get from the form instance:

  • values(): returns the entire form values from all registered form controls.
  • value(formControlName: string) : returns a specific form control value by its name.
  • formData(): returns the entire form values as FormData object.
  • controls(): returns all registered form controls.
  • control(name: string): returns a specific form control by its name.
  • isValid(): returns true if all form controls are valid, otherwise, it returns false.
  • submit(): submits the form programatically.
  • isSubmitting(): returns true if the form is submitting, otherwise, it returns false.
  • submitting(submitForm: boolean): sets the form submitting state.
  • reset(): resets the form to its initial state.
  • resetErrors(): resets all form controls errors.
  • change(name: string, value: any): changes a specific form control value.
  • id: returns the form id.
  • formElement: returns the form element.

Set form component

You can set the form component by using component prop.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm} component="form">
      <TextInput name="name" required />
      <TextInput name="username" />
      <button>Submit</button>
    </Form>
  );
}

you ccan pass any React component, but it must receive a ref prop and be attached to the internal form element of that component.

Capturing form errors

If you want to capture all invalid form contrls, use onError prop.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  const onError = (formControls) => {
    const errors = formControls.map((control) => control.error);
    console.log(errors);
  };

  return (
    <Form onSubmit={submitForm} onError={onError}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <button>Submit</button>
    </Form>
  );
}

useForm hook

You can use useForm hook to get the form instance and submit the form programatically.

// src/InternalComponent.tsx
import { useForm } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function InternalComponent() {
  const form = useForm();

  const submitForm = () => {
    form?.submit();
  };

  return <TextInput name="name" onChange={submitForm} required />;
}

You can use useForm hook only inside the Form component.

If useForm is used outside the Form component, it will return null.

Hidden Input

In some cases, we need to add hidden inputs to the form, this will not be visible to the user nor will be validated, also no UI will be rendered.

import { HiddenInput } from "@mongez/react-form";

export default function App() {
  return (
    <Form onSubmit={submitForm}>
      <HiddenInput name="userId" value={1} />
      <TextInput name="name" required />
      <TextInput name="username" />
      <button>Submit</button>
    </Form>
  );
}

useSubmitButton hook

Another good hook to use is useSubmitButton, this hook basically disables the submit button in certain scenarios

  • When the form has been submitted.
  • When there are invalid form controls.

Also the buttion is switch to enabled state when the form is valid, or form is reset, or form submission is false.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";
import SubmitButton from "./components/SubmitButton";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <SubmitButton>Submit</SubmitButton>
    </Form>
  );
}
// src/components/SubmitButton.tsx
import { useSubmitButton } from "@mongez/react-form";

export default function SubmitButton({ children }) {
  const { disabled } = useSubmitButton();

  return <button disabled={disabled}>{children}</button>;
}

It will be updated automatically.

You can also get notified when the form is being submitted only besides the disabled state, it could be useful to display a loading indicator.

// src/components/SubmitButton.tsx
import { useSubmitButton } from "@mongez/react-form";

export default function SubmitButton({ children }) {
  const { disabled, isSubmitting } = useSubmitButton();

  return (
    <button disabled={disabled}>
      {isSubmitting ? "Loading..." : children}
    </button>
  );
}

Change form submitting state

Let's take a scenario, where the form is submitted, an API request is sent to the server, and the form is being submitted, but the server returns an error, in this case we want to change the form submitting state to false so the user can submit the form again.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";
import SubmitButton from "./components/SubmitButton";
import createAccount from "./services/createAccount";

export default function App() {
  const submitForm = ({ values }) => {
    createAccount(values)
      .then((response) => {
        //...
      })
      .catch((error) => {
        form?.submitting(false);
      });
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <SubmitButton>Submit</SubmitButton>
    </Form>
  );
}

This will change the form submitting state to false and the submit button will be enabled again.

Active Forms

All forms are being tracked using the Active Forms utilities, which means you can get the current active form from anywhere in the project using getActiveForm utility.

import { getActiveForm } from "@mongez/react-form";

console.log(getActiveForm()); // null by default

By default the active form will be null until there is a form is mounted in the DOM, once there is a Form rendered you can get access to that form using the getActiveForm function.

import { getActiveForm } from "@mongez/react-form";

export default function LoginPage() {
  React.useEffect(() => {
    console.log(getActiveForm()); // will get the Form Component Which implements FormInterface
  }, []);

  return (
    <>
      <LoginFormComponent />
    </>
  );
}

Sometimes we may open multiple forms in one page, for example a single page that displays the login form and the register form, we can access any form of them using the getForm utility by passing the form id to it.

import { getForm, Form } from "@mongez/react-form";

export default function LoginPage() {
  React.useEffect(() => {
    console.log(getForm("login-form")); // will get the login form
    console.log(getForm("register-form")); // will get the register form
  }, []);

  return (
    <>
      <Form id="login-form">...</Form>
      <Form id="register-form">...</Form>
    </>
  );
}

Reset Form

You can reset the form using reset method, this will return all form controls values to its initial value.

// src/App.tsx
import { Form, getActiveForm } from "@mongez/react-form";
import TextInput from "./components/TextInput";
import SubmitButton from "./components/SubmitButton";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  const resetForm = () => {
    const form = getActiveForm();

    form?.reset();
  };

  return (
    <Form onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <SubmitButton>Submit</SubmitButton>
      <button onClick={resetForm}>Reset</button>
    </Form>
  );
}

Form Default Value

Added in V3.0.0

Instead of setting defaultValue to each single form control, you can set the default values to the form itself.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";
import SubmitButton from "./components/SubmitButton";

export default function App() {
  const submitForm = ({ values }) => {
    console.log(values);
  };

  return (
    <Form
      onSubmit={submitForm}
      defaultValue={{ name: "John Doe", username: "john" }}
    >
      <TextInput name="name" required />
      <TextInput name="username" />
      <SubmitButton>Submit</SubmitButton>
    </Form>
  );
}

This will set the default values to the form controls, and if the user changes the value, the form control value will be updated.

Form Ref

You can also get the form instance using the ref prop.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";
import SubmitButton from "./components/SubmitButton";
import { useRef } from "react";

export default function App() {
  const formRef = useRef();

  const submitForm = ({ values }) => {
    console.log(values);
  };

  const resetForm = () => {
    formRef.current.reset();
  };

  return (
    <Form ref={formRef} onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <SubmitButton>Submit</SubmitButton>
      <button onClick={resetForm}>Reset</button>
    </Form>
  );
}

Form Events

You can listen to form events using the on method, it's basically the one that's used in useSubmitButton hook.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";
import SubmitButton from "./components/SubmitButton";
import { useEffect } from "react";

export default function App() {
  const formRef = useRef();

  const submitForm = ({ values }) => {
    console.log(values);
  };

  useEffect(() => {
    formRef.current.on("submitting", () => {
      console.log("Form is being submitted");
    });

    formRef.current.on("submit", () => {
      console.log("Form is submitted");
    });
  }, []);

  return (
    <Form ref={formRef} onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <SubmitButton>Submit</SubmitButton>
    </Form>
  );
}

Here are the available events:

  • submitting: will be triggered when the form is being submitted, recives a Boolean value to indicate if the form is being submitted or not.
  • submit: will be triggered when the form is submitted, if form submitting is set to false, this event will be fired immediately.
  • resetting: will be triggered when the form is being reset, recives a Boolean value to indicate if the form is being reset or not.
  • reset: will be triggered when the form is reset.
  • validating: will be triggered when the form is being validated.
  • invalidControls: will be triggered when the form has invalid controls, recives an array of invalid controls.
  • validControls: will be triggered when the form has valid controls, recives an array of valid controls.
  • validation: will be triggered when the form is validated, recives a Boolean value to indicate if the form is valid or not, also recives an array of all controls that have been validated.

Validate Stepper

Sometimes you may deal with a form that has multiple steps, and you want to validate each step before moving to the next one, you can use validateVisible method to do that.

First off, make sure the elements are hidden and not removed from the DOM, otherwise, the validation will not work.

Secondly, each input must use the visibleElementRef either in the input itself or the wrapper, this way the form will know which inputs are visible and which are not.

// src/components/TextInput.tsx
import { Form, useFormControl } from "@mongez/react-form";

export default function TextInput(props: FormControlProps) {
  const { value, changeValue, type, error, visibleElementRef } =
    useFormControl(props);

  return (
    <div ref={visibleElementRef}>
      <input
        type={type}
        value={value}
        onChange={(e) => {
          changeValue(e.target.value);
        }}
      />
      {error && (
        <span
          style={{
            color: "red",
          }}
        >
          {error}
        </span>
      )}
    </div>
  );
}

Finally, when the usere clicks on the next button, validate the current step, if it's valid, move to the next step, otherwise, show the errors, but this time instead of using validate methid, we will use validateVisible method.

// src/App.tsx
import { Form } from "@mongez/react-form";
import TextInput from "./components/TextInput";

export default function App() {
  const formRef = useRef();

  const submitForm = ({ values }) => {
    console.log(values);
  };

  const nextStep = async () => {
    const form = formRef.current;

    form.validateVisible().then(() => {
      if (form.isValid()) {
        // move to the next step
      }
    });
  };

  return (
    <Form ref={formRef} onSubmit={submitForm}>
      <TextInput name="name" required />
      <TextInput name="username" />
      <button type="button" onClick={nextStep}>
        Next
      </button>
    </Form>
  );
}

When the validation is done, the output of the promise returns list of the inputs which have been validated either they are valid or not.

TODO

  • Add strongRule to validate strong password.
  • Add silent update

Package Sidebar

Install

npm i @mongez/react-form

Weekly Downloads

22

Version

3.0.16

License

MIT

Unpacked Size

358 kB

Total Files

248

Last publish

Collaborators

  • hassanzohdy