In React Native, how do a TextInput's onChangeText and onChange callbacks differ, and when would you pass defaultValue instead of value?
answer
- the native field owns the text
- one callback gets a string
- the other gets nativeEvent
- eventCount rides along
- defaultValue: initial text only
basics
~20 sonChangeText receives the new text as a plain string; onChange receives the native event, whose nativeEvent carries text, eventCount and target. Both fire for the same edit. defaultValue seeds an uncontrolled field; value makes the native field match your state.
solid answer
~40 sEvery edit in a React Native `TextInput` produces one native change event. `TextInput` calls `onChange` with the full event first, whose `nativeEvent` holds `text`, `eventCount` and `target`, and then `onChangeText` with just the new string. Most code only needs `onChangeText`; `onChange` is for when you need the event itself. `value` makes the field controlled: after each render React Native forces the native text to match it. `defaultValue` only provides the initial text; from then on the native field owns the text and you read it when you need it, for example from `nativeEvent.text` in `onEndEditing` or `onSubmitEditing`. Uncontrolled fields avoid a render per keystroke, which suits long forms whose values you only need on submit.
code
tsx · 29 linesimport {useState} from 'react';
import {TextInput} from 'react-native';
import type {TextInputChangeEvent} from 'react-native';
export function StreetField({saved}: {saved: string}) {
const [street, setStreet] = useState(saved);
return (
<TextInput
value={street}
onChangeText={setStreet}
onChange={(e: TextInputChangeEvent) =>
console.log('edit', e.nativeEvent.eventCount)
}
placeholder="Street address"
/>
);
}
export function UnitField({saved, onDone}: {saved: string; onDone: (t: string) => void}) {
// Uncontrolled: the native field owns the text; read it when editing ends.
return (
<TextInput
defaultValue={saved}
onEndEditing={e => onDone(e.nativeEvent.text)}
placeholder="Apartment or unit"
/>
);
}go deeper
Recall that onChangeText gives a string and onChange gives the native event, and that value controls the field while defaultValue only seeds it.
Explain that the native field owns the text, the order of the two callbacks, and how value is pushed back to the native view after a render.
Choose controlled or uncontrolled per form based on validation needs and render cost, and read uncontrolled values through onEndEditing, onSubmitEditing or a ref.
Set a form convention for the app, weighing live feedback against per-keystroke renders on low-end devices and consistency across teams.
## Where the text actually lives A React Native `TextInput` is a real platform text field: a `UITextField` or `UITextView` on iOS, an `EditText` on Android. The **native field owns the characters** the user sees. When the user types, the platform updates the field immediately and React Native then tells JavaScript what happened. That ordering explains most of the API. ## The two change callbacks Each edit produces one native change event, and `TextInput` fans it out to two props, in this order: 1. **`onChange(event)`** receives the event object. Its `nativeEvent` contains: - `text`, the full new text; - `eventCount`, a counter the native field increments on every change; - `target`, the native node that fired. 2. **`onChangeText(text)`** receives only the new text as a string. | | `onChangeText` | `onChange` | |---|---|---| | Argument | `string` | event with `nativeEvent: {text, eventCount, target}` | | Typical use | updating state for a field | reading the event, or a shared handler that needs `target` | | Fires | on every edit | on every edit, just before `onChangeText` | `onChangeText` is the idiomatic choice. A generic handler that needs more than the text uses `onChange`. You rarely need both on one field. ## value: controlled Passing `value` makes the field **controlled**. After each render, if `value` differs from the last text the native field reported, React Native sends the new text down to the native view. The `eventCount` from the change event is part of that exchange: the native side ignores an update from JavaScript that was computed from an older event than the one it has already applied, so a slow render cannot overwrite fresher typing. Controlled is right when the UI must react to every keystroke: - live validation or a character counter; - enabling a button as soon as the field is non-empty; - deriving other state from the text as it changes. The cost is a JavaScript render per keystroke, and the forced sync can flicker if you rewrite or reject what the user typed. ## defaultValue: uncontrolled `defaultValue` gives the field its **initial text** and nothing more. The native field then owns the text and nothing is forced back. You read the value when you need it: - `onEndEditing` and `onSubmitEditing` both receive `nativeEvent.text`; - `onChangeText` can still record the latest text in a ref without causing renders; - `ref.current?.clear()` empties the field without touching any state. The docs warn that `onBlur`'s `nativeEvent.text` may be `undefined`; use `onEndEditing` to get the final text instead. ## Common mistakes - **Reading `event.target.value`.** That is a browser habit; in React Native the text is the string passed to `onChangeText`, or `event.nativeEvent.text` in `onChange`. - **Passing `value` without a change handler.** The native field still accepts the keystroke, then React Native forces it back to the unchanged `value`, so the field looks broken: characters appear and vanish. Either handle `onChangeText` or make the field non-editable with `editable={false}`. - **Doing heavy work in `onChangeText`.** It runs on every keystroke on the JavaScript thread; expensive validation or a render of the whole screen per character makes typing feel sluggish. - **Expecting a diff.** `onChangeText` always receives the complete current text, not the characters just typed. ## Choosing in a checkout form A grocery checkout collects a street address, an apartment or unit, a postcode and a phone number for the courier. Two sensible designs: - **Controlled fields** if the form validates as the user types and enables **Place order** live. Four small fields re-rendering per keystroke is cheap. - **Uncontrolled fields** with `defaultValue` prefilled from the saved address, reading each value on submit, if the form only validates at the end. This avoids re-rendering the whole form on each keystroke, which matters on a slow device or a large form. Mixing both on one field is a bug: pass `value` or `defaultValue`, not both, and do not switch a field between them during its life.
- In React Native, how do you read the final text of an uncontrolled TextInput without tracking every keystroke in state?Read it from the events that carry it: `onEndEditing` and `onSubmitEditing` both pass `nativeEvent.text`. Alternatively keep the latest string from `onChangeText` in a ref, which does not trigger renders. Avoid relying on `onBlur`'s `nativeEvent.text`, which the docs warn can be `undefined`.
- In React Native, what is eventCount in a TextInput's onChange event used for?It is a counter the native field increments on every change. `TextInput` remembers the latest count and sends it along when it pushes a controlled `value` back to the native view. The native side ignores an update whose count is not its current one, so a render based on an older keystroke cannot overwrite text the user has typed since.
saying these in an interview costs you the question
- onChangeText receives an event object and you read event.target.value from it.
- onChange and onChangeText fire for different kinds of edits, so a field needs both.
- defaultValue keeps the field in sync whenever the prop changes later.
- Without value the text is lost, so an uncontrolled TextInput cannot be read on submit.
- A controlled TextInput renders the keystroke only after JavaScript sets state.