React Native Usage
Apply this skill any time the user is integrating the library into a React Native or Expo project. The form engine, validation rules, events, and the useFormControl hook are 100% identical between Web and RN — only the top-level form component and the input host element differ.
Import the right component
Web uses Form. React Native uses NativeForm. Same props, same context, same FormInterface:
import { NativeForm } from "@mongez/react-form";Do not mix them — use NativeForm exclusively in RN code paths.
Why a separate component?
Form renders an HTML <form> element, calls requestSubmit(), and uses event.preventDefault() to cancel the browser-default submit. None of those exist in RN. NativeForm:
- Renders a React Fragment by default (no host element added to the tree).
- Has no DOM submit event;
form.submit()runs the shared validate-then-onSubmitpipeline directly. - Does not add
react-nativeas a peer dependency — the Fragment default keeps the package usable without RN installed.
Minimal RN form
import { NativeForm } from "@mongez/react-form";import TextInput from "./components/TextInput";import SubmitButton from "./components/SubmitButton";
export default function App() { const handleSubmit = ({ values }) => { console.log(values); };
return ( <NativeForm onSubmit={handleSubmit}> <TextInput name="firstName" required /> <TextInput name="lastName" /> <SubmitButton>Submit</SubmitButton> </NativeForm> );}Adding a wrapper View
Pass component={View} to wrap children in a real host element. The ref of that component lands on form.formElement:
import { View } from "react-native";
<NativeForm onSubmit={handleSubmit} component={View} style={{ padding: 16, gap: 12 }}> {/* ... */}</NativeForm>Any extra props (style, accessibilityRole, etc.) flow through to View.
Writing an RN form control
import { useFormControl, type FormControlProps } from "@mongez/react-form";import { TextInput as RNTextInput, Text, View } from "react-native";
export default function TextInput(props: FormControlProps) { const { value, changeValue, inputRef, formControl, error, disabled } = useFormControl(props);
return ( <View> <RNTextInput ref={inputRef} value={value} onChangeText={changeValue} onFocus={() => (formControl.isTouched = true)} editable={!disabled} /> {error && <Text style={{ color: "red" }}>{error}</Text>} </View> );}Wiring notes:
onChangeTexton RN’sTextInputemits the string directly — pass it straight tochangeValue. Do NOT useonChange(which emits an event object) unless you handle the unwrap yourself.ref={inputRef}enablesformControl.focus()/formControl.blur()(RN’sTextInputref exposes both methods natively).- The auto-touch listener that Web uses (a DOM
focusevent) is a no-op on RN. If you wantformControl.isTouchedto track, set it fromonFocusas shown. - For numeric inputs, set
keyboardType="numeric"on theRNTextInputANDtype="number"on the form control (sonumberRule/integerRule/floatRuleapply).
Submit button for RN
import { useForm, useSubmitButton } from "@mongez/react-form";import { Pressable, Text } from "react-native";
export default function SubmitButton({ children }: { children: React.ReactNode }) { const form = useForm(); const { disabled, isSubmitting } = useSubmitButton();
return ( <Pressable disabled={disabled} onPress={() => form?.submit()} style={{ opacity: disabled ? 0.5 : 1 }} > <Text>{isSubmitting ? "Submitting..." : children}</Text> </Pressable> );}form?.submit() is required — there is no equivalent of <button type="submit"> on RN. See the submit-button skill for details.
Two caveats vs Web
formControl.isVisible()always returnstrueon RN. There is no DOM tree to walk, so the visibility check cannot detect hidden ancestors. This meansform.validateVisible()behaves identically toform.validate()on RN. Usually fine because RN steppers typically unmount inactive steps rather than hide them.- The auto-touch DOM listener is a no-op. Set
formControl.isTouched = truemanually in your input’sonFocus(as shown above) if you need touched-state tracking.
Checkboxes on RN
There’s no native checkbox primitive in RN. Build one from Pressable + useFormControl({ type: "checkbox" }):
import { useFormControl, type FormControlProps } from "@mongez/react-form";import { Pressable, Text } from "react-native";
export default function Checkbox(props: FormControlProps & { label: string }) { const { checked, setChecked } = useFormControl({ ...props, type: "checkbox" });
return ( <Pressable onPress={() => setChecked(!checked)}> <Text>{checked ? "[x]" : "[ ]"} {props.label}</Text> </Pressable> );}What carries over unchanged from Web
- The validation rules system, including all built-in rules.
useForm,useSubmitButton,useRadioInput,useFormControl.FormContextanduseForm()consumption.getActiveForm(),getForm(id)helpers.- All form events (
submit,submitting,validating,validation,dirty,reset, etc.) — seeform-events. - Default-value collection via
<NativeForm defaultValue={{ ... }}>. - Dot-notation
name="user.firstName"produces nested values.
Cross-platform shared components
If you need a single input component that works on both Web and RN (e.g. a shared component library), branch on Platform.OS inside the component, or build separate .web.tsx / .native.tsx files and let Metro/the bundler pick the right one. The useFormControl call itself is identical between them — only the host element changes.