@mongez/react-form
v3.0.16
Published
A Powerful React form handler.
Downloads
391
Maintainers
Readme
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 ofuser.name
notation, it will be converted intouser.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 avalue
property from theuseFormControl
hook regardless of the input type, and you can change the value using thechangeValue
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 theminLength
prop.- Requires
minLength
prop to be present. - Translation Key:
validation.minLength
, receives:length
as a placeholder. minLength
prop will be preserved from being passed tootherProps
.- Works with strings and arrays.
- Requires
maxLengthRule
: Check if the value's length is less than or equal to themaxLength
prop.- Requires
maxLength
prop to be present. - Translation Key:
validation.maxLength
, receives:length
as a placeholder. maxLength
prop will be preserved from being passed tootherProps
.- Works with strings and arrays.
- Requires
lengthRule
: Check if the value's length is equal to thelength
prop.- Requires
length
prop to be present. - Translation Key:
validation.length
, receives:length
as a placeholder. length
prop will be preserved from being passed tootherProps
.- Works with strings and arrays.
- Requires
minRule
: Check if the value is greater than or equal to themin
prop.- Requires
min
prop to be present. - Translation Key:
validation.min
, receives:min
as a placeholder. min
prop will be preserved from being passed tootherProps
.- Works with numbers.
- Requires
maxRule
: Check if the value is less than or equal to themax
prop.- Requires
max
prop to be present. - Translation Key:
validation.max
, receives:max
as a placeholder. max
prop will be preserved from being passed tootherProps
.- Works with numbers.
- Requires
emailRule
: Check if the value is a valid email.- Translation Key:
validation.email
. - Requires
type
prop to beemail
.
- Translation Key:
numberRule
: Check if the value is a valid number.- Translation Key:
validation.number
. - Requires
type
prop to benumber
.
- Translation Key:
floatRule
: Check if the value is a valid float number.- Translation Key:
validation.float
. - Requires
type
prop to befloat
.
- Translation Key:
integerRule
: Check if the value is a valid integer number.- Translation Key:
validation.integer
. - Requires
type
prop to beinteger
.
- Translation Key:
patternRule
: Check if the value matches thepattern
prop.- Requires
pattern
prop to be present. - Translation Key:
validation.pattern
, receives:pattern
as a placeholder. pattern
prop will be preserved from being passed tootherProps
.
- Requires
alphabetRule
: Check if the value is a valid alphabet.- Translation Key:
validation.alphabet
. - Requires
type
prop to bealphabet
.
- Translation Key:
matchRule
: Check if the value matches the value of the input with the name of thematch
prop.- Requires
match
prop to be present. - Translation Key:
validation.match
, receives:matchingInput
as a placeholder. match
prop will be preserved from being passed tootherProps
.url
type is also supported, you must set the input type tourl
to make it work and addurlRule
as well.
- Requires
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:
- Overriding the translation errors.
- Changing the error keys per component call.
- 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 onSubmit
prop 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 theonSubmit
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 asFormData
object.controls()
: returns all registered form controls.control(name: string)
: returns a specific form control by its name.isValid()
: returnstrue
if all form controls are valid, otherwise, it returnsfalse
.submit()
: submits the form programatically.isSubmitting()
: returnstrue
if the form is submitting, otherwise, it returnsfalse
.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 theForm
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 aBoolean
value to indicate if the form is being submitted or not.submit
: will be triggered when the form is submitted, if formsubmitting
is set to false, this event will be fired immediately.resetting
: will be triggered when the form is being reset, recives aBoolean
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 aBoolean
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