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.
v4.
NativeFormis aforwardReffunction component over the React-freeFormEngineclass; arefto it resolves to the engine (typedFormInterface). Everything else below is unchanged from the v3 surface — plus the new v4 hooks (useFieldArray,useWatch) and engine methods (fill,setValues,setErrors) work identically on RN.
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. Both components share the same FormEngine and only inject a platform-specific submit trigger (web dispatches a native submit event; native calls the engine’s pipeline directly). NativeForm:
- Renders a React Fragment by default (no host element added to the tree).
- Has no DOM submit event;
form.submit()delegates to the engine’shandleSubmit()pipeline (validate →onSubmit). - 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).
getInputProps on React Native (v4)
getInputProps() works on RN too, but its onChange unwraps a DOM-style e.target.value, which RN’s TextInput does not emit. Override onChange with an RN-friendly handler (and read the rest of the bag for ref / value / disabled / ARIA-equivalent props):
const { changeValue, onBlur, getInputProps } = useFormControl(props);
<RNTextInput {...getInputProps({ onChange: undefined, // drop the DOM-style handler onChangeText: changeValue, // RN emits the string directly onBlur, // keep blur wiring (validateOn="blur") })}/>;Or skip the bag entirely on RN and wire value / onChangeText / inputRef / onBlur by hand as in the example above — both are valid. The aria-* keys from getInputProps are harmless on RN (ignored by TextInput); use accessibilityState/accessibilityLabel for native a11y instead.
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, async validation, and per-field Standard Schema.
useForm,useSubmitButton,useRadioInput,useFormControl, and the v4 hooksuseFieldArray/useWatch.FormContextanduseForm()consumption.getActiveForm(),getForm(id)helpers.- All form events (
submit,submitting,validating,validation,dirty,reset, the v4change, etc.) — seeform-events. - Default-value collection via
<NativeForm defaultValue={{ ... }}>, the reactivevaluesprop, andform.fill/setValues/setErrorshydration — seeform-hydration. - Whole-form Standard Schema via
<NativeForm schema={...}>— seestandard-schema-validation. - 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.