react-native-transformer-text-input
v0.1.0
Published
TextInput component that allows transforming text synchronously with a worklet
Maintainers
Readme
TextInput component that allows transforming text synchronously with a worklet.
About
App & Flow is a Montreal-based React Native engineering and consulting studio. We partner with the world’s top companies and are recommended by Expo. Need a hand? Let’s build together. [email protected]
Demo
https://github.com/user-attachments/assets/b22041b2-f5c1-4b2a-9fd0-960d7f5b3cf9
Motivation
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.
Installation
npm install react-native-transformer-text-inputUsage
import { 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}
/>
);
}API
Transformer
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
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.
Notes
- 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
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.
Built-in Transformers (Experimental)
Warning: Built-in transformers are experimental. Breaking changes may occur in minor versions.
The library includes ready-to-use transformers for common use cases.
PatternTransformer
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).
PhoneNumberTransformer
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"
/>Acknowledgments
- 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.
Contributing
License
MIT
Made with create-react-native-library
