In a Detox test of a React Native sign-up form, why can replaceText leave the screen in a different state than typeText?
answer
- system keyboard vs direct set
- per-keystroke callbacks
- submit button stays disabled
- choose per field
- keyboard can cover the next button
basics
~20 sDetox's typeText enters text through the system keyboard, firing the same change events a user would, while replaceText sets the value directly and may skip input callbacks. Validation, masks or submit-enable logic driven by those callbacks can then disagree with what the field shows.
solid answer
~40 s`typeText` goes through the system keyboard, so each keystroke reaches the `TextInput` and its `onChangeText`, formatters and key handlers run as they would for a user. `replaceText` sets the final value directly; it is faster, but Detox warns it may not trigger all text input callbacks. On a meal-kit sign-up, the postcode validation and the enabled state of "Create account" hang off those callbacks, so after `replaceText` the form can look filled while the button stays disabled. I use `typeText` for fields with logic, `replaceText` only for plain free-text fields as a deliberate speed-up, and `tapReturnKey()` when submission from the keyboard matters — remembering the real keyboard can cover the next button.
code
typescript · 11 linesimport { by, element, expect } from 'detox';
it('enables Create account only after valid input', async () => {
await expect(element(by.id('signup.submit'))).toBeVisible();
await element(by.id('signup.email')).typeText('[email protected]');
await element(by.id('signup.password')).typeText('correct-horse-42');
await element(by.id('signup.postcode')).typeText('E1 6AN');
await element(by.id('signup.notes')).replaceText('Leave the box by the side gate.');
await element(by.id('signup.postcode')).tapReturnKey();
await element(by.id('signup.submit')).tap();
});go deeper
Recall that typeText uses the keyboard and replaceText sets the value directly, and that typeText is closer to a real user.
Explain which TextInput callbacks depend on keystrokes and why skipping them can leave the form in an inconsistent state.
Pick the action per field in a real flow, handle the keyboard covering controls, and trade test speed against fidelity knowingly.
Agree team rules on when a faster but less faithful input path is acceptable in shared end-to-end suites.
## Three ways to put text into a field **Detox** offers three text actions on an element found by a matcher, typically a React Native `TextInput` with a `testID`: | Action | How it enters text | Speed | Fires per-keystroke handlers | |---|---|---|---| | `typeText('abc')` | through the system keyboard and its typing behaviour | slower | yes, as a user would | | `replaceText('abc')` | sets the text directly, no keyboard | faster | not necessarily | | `clearText()` | deletes through the keyboard | — | yes | Related helpers are `tapReturnKey()` and `tapBackspaceKey()`, which press those keys through the keyboard. On iOS, before typing or replacing, Detox tries to make the element the first responder (and its window the key window), so the field does not need a tap first. ## Why the results can differ The Detox docs warn that `replaceText` "may not trigger all text input callbacks, causing an undefined state in your app". In React Native terms, a `TextInput` screen often reacts to **each change**: - `onChangeText` updates form state and runs validation, such as the postcode check on the meal-kit sign-up. - A mask or formatter rewrites the value as it is typed (a phone number with spaces, a card number in groups). - `onKeyPress` or `onSubmitEditing` drive focus moves and submission. `typeText` produces the same sequence of events a person would, so all of that runs. `replaceText` jumps straight to the final value, so a handler that only runs on keystrokes, or a formatter that expects gradual input, can be skipped or see a different sequence. The symptom is a form that looks filled but whose submit button stays disabled, or whose state holds a value the UI does not show. ## Choosing per field in the sign-up 1. **Email, password, postcode** — use `typeText`. Validation and the enabled state of "Create account" depend on the change handlers; the test should exercise them. 2. **A long free-text field with no per-change logic** (delivery instructions) — `replaceText` is an acceptable speed-up. 3. **Editing a pre-filled value** — `clearText()` then `typeText(...)`, or `replaceText` if nothing listens to changes. 4. **Submitting from the keyboard** — `tapReturnKey()` on the last field exercises `onSubmitEditing`, which a tap on the button would not. ```js await element(by.id('signup.email')).typeText('[email protected]'); await element(by.id('signup.password')).typeText('correct-horse-42'); await element(by.id('signup.postcode')).typeText('E1 6AN'); await element(by.id('signup.notes')).replaceText('Leave the box by the side gate, please.'); await element(by.id('signup.postcode')).tapReturnKey(); await expect(element(by.id('signup.submit'))).toBeVisible(); ``` ## Side effects of the real keyboard - **The keyboard takes screen space.** After `typeText`, a button near the bottom can end up covered, and a later `tap()` or `toBeVisible()` on it can fail. Dismissing the keyboard (`tapReturnKey()` when the field's return key does that) or scrolling the form before tapping keeps the test honest about what the user sees. - **Keyboard settings matter.** Autocorrect, autocapitalisation and a hardware-keyboard setting on a simulator can change what ends up in the field. Setting the relevant `TextInput` props for emails and passwords (for example `autoCapitalize="none"`) keeps real typing predictable. - **Speed.** Typing is slower than replacing. On a long suite, `replaceText` for fields without logic is a legitimate optimisation — as long as it is a deliberate choice, not the default. ## Reading back what was entered After entering text, assert it rather than assuming it: - `await expect(element(by.id('signup.postcode'))).toHaveText('E1 6AN')` checks the field's content; a mask that reformats input shows up here immediately. - `await element(by.id('signup.postcode')).getAttributes()` returns an object whose `text` attribute can be compared with Jest's own `expect` (imported from the `expect` package, since Detox's `expect` is the global). - `await expect(element(by.id('signup.password'))).toBeFocused()` after `tapReturnKey()` on the email field confirms the form moves focus the way the screen's submit handlers intend. A test that types and then asserts the rendered value catches the most common `replaceText` surprise — a formatter that never ran. ## What interviewers want to hear A strong answer says that `typeText` simulates the user and fires the input callbacks, `replaceText` is a faster direct set that can bypass them, and that the choice is made **per field** based on whether the app's behaviour depends on keystrokes. Mentioning the keyboard covering the next control shows real experience with sign-up flows.
- Why might a tap on the submit button fail right after typeText on the last field?The system keyboard is still up and can cover a button near the bottom of the screen, so the element is not hittable or not visible enough. Dismiss the keyboard first, for example with `tapReturnKey()` when the field's return key closes it, or scroll the form so the button is in view before tapping.
- When is replaceText a sound choice?For a field whose value the app only reads on submit — long delivery notes, a search box without live suggestions — where no validation, mask or focus logic runs per change. It saves time on long suites; the rule is to choose it deliberately per field, never as the default for every input.
saying these in an interview costs you the question
- replaceText and typeText always leave identical app state.
- replaceText is the default choice because it is faster.
- typeText bypasses onChangeText and writes the value natively.
- The keyboard never affects later taps in the test.
- Fields must be tapped before typeText on iOS.