TextInput component that allows transforming text synchronously with a worklet.
Transforming input as users type is common — phone numbers, credit cards, usernames. Existing approaches have trade-offs:
Pattern-based masking (e.g., react-native-advanced-input-mask) uses declarative patterns like +1 ([000]) [000]-[0000]. This works well for fixed formats, but patterns can't express conditional logic, variable-length formats, or transformations that depend on context.
Controlled inputs (value + onChangeText + state) give you full JS flexibility, but create a native → JS → re-render → native round-trip. This causes visible lag and cursor flicker—the input feels sluggish because keystrokes are corrected asynchronously.
This library combines JS flexibility with synchronous execution. Your transform runs as a worklet on the UI thread — no bridge delay, no flicker. Write any logic you need with the responsiveness of a native input.
npm install react-native-transformer-text-inputimport { useRef } from 'react';
import {
Transformer,
TransformerTextInput,
type TransformerTextInputInstance,
} from 'react-native-transformer-text-input';
// Transformer that formats input as a lowercase username with @ prefix
const usernameTransformer = new Transformer(({ value }) => {
'worklet';
const cleaned = value.replace(/[^0-9a-zA-Z]/g, '').toLowerCase();
return { value: cleaned ? '@' + cleaned : '' };
});
function UsernameTextInput() {
const inputRef = useRef<TransformerTextInputInstance>(null);
const handleSubmit = () => {
const username = inputRef.current?.getValue();
console.log('Submitted:', username);
};
return (
<TransformerTextInput
ref={inputRef}
transformer={usernameTransformer}
placeholder="@username"
autoCapitalize="none"
autoCorrect={false}
onSubmitEditing={handleSubmit}
/>
);
}Create a transformer by passing a worklet function:
- Constructor:
new Transformer(worklet) - worklet input: an object with
value: current text value.previousValue: previous text value (falls back tovalueon first call).selection: current selection{ start, end }.previousSelection: previous selection{ start, end }(falls back toselectionon first call).
- worklet return:
- Return
nullorundefinedto apply no transform. - Return an object where each field can also be
nullorundefinedto leave that part unchanged:value?: string | nullto update the text.selection?: { start: number; end: number } | nullto update the selection.
- Return
TransformerTextInput wraps React Native TextInput and applies a Transformer on the UI thread.
- Props: all
TextInputprops (exceptvalue) plus:transformer: aTransformerinstance.
- Ref:
TransformerTextInputInstancewith:getValue(): string- Returns the current text value.update(options): void- Programmatically update the input.options.value: string- The new text value.options.selection?: { start: number; end: number }- Optional cursor/selection position.options.transform?: boolean- Whether to run the transformer on the new value (default:true).
clear(): void- Clear the input value.
- The transformer must be a worklet; the
Transformerconstructor will throw if it isn't. - Prefer creating
Transformerinstances at module scope to avoid recreating worklets on every render. - This library supports the New Architecture only.
Selection control is needed because transforms can insert or remove characters, which would otherwise move the cursor unpredictably. The transformer can return a selection to fully control the caret/selection after a change.
Default behavior when no selection is returned:
- If the cursor was at the end, it stays at the end.
- If the cursor was in the middle, it moves forward by the number of inserted/removed characters.
- If the position is ambiguous, it falls back to the end.
Warning: Built-in transformers are experimental. Breaking changes may occur in minor versions.
The library includes ready-to-use transformers for common use cases.
Formats input using a pattern string with placeholder characters.
import { PatternTransformer } from 'react-native-transformer-text-input/formatters/pattern';
const dateTransformer = new PatternTransformer({
pattern: '##/##/####', // # = digit, A = letter, * = alphanumeric
});
// Formats as: 12/31/2024
<TransformerTextInput
transformer={dateTransformer}
keyboardType="number-pad"
/>Options:
pattern: Pattern string where#matches digits,Amatches letters,*matches alphanumeric.definitions: Custom placeholder definitions (e.g.,{ 'X': /[0-9A-F]/i }for hex).showTrailingLiterals: Show literal characters after the last input (default:false).
Formats phone numbers as the user types.
import { PhoneNumberTransformer } from 'react-native-transformer-text-input/formatters/phone-number';
const phoneTransformer = new PhoneNumberTransformer({
country: 'US', // Only 'US' supported currently
debug: false, // Enable debug logging (default: false)
});
// Formats as: +1 (555) 123-4567
<TransformerTextInput
transformer={phoneTransformer}
keyboardType="phone-pad"
/>- react-native-live-markdown and react-native-advanced-input-mask for examples of how to extend TextInput.
- react-native-worklets for the worklet runtime powering UI-thread execution.
MIT
Made with create-react-native-library