This is documentation for the next SDK version. For up-to-date documentation, see the latest version (SDK 55).
useNativeState
A React hook that creates observable state shared between JavaScript and native Jetpack Compose views.
For the complete documentation index, see llms.txt. Use this file to discover all available pages.
useNativeState returns an ObservableState that maps to a Compose MutableState on the native side, so reads and writes to .value are tracked directly by Compose without going through the React render cycle. This lets you update the native view synchronously from a worklet on the UI thread.
Installation
- npx expo install @expo/uiIf you are installing this in an existing React Native app, make sure to install expo in your project.
Usage
Note: Using worklets requires installing
react-native-reanimatedandreact-native-workletsin your project.useNativeStateitself works without them, but the synchronous UI-thread updates shown below depend on the worklet runtime.
The example below masks a phone number as the user types. The formatting and the writes to maskedPhone.value (text) and selection.value (cursor position) all happen synchronously on the UI thread, so there is no flicker between the typed value and the masked value.
import { Host, TextField, Text as ComposeText, useNativeState } from '@expo/ui/jetpack-compose'; import { fillMaxWidth } from '@expo/ui/jetpack-compose/modifiers'; import { useEffectEvent } from 'react'; export default function WorkletPhoneMaskExample() { const maskedPhone = useNativeState(''); const selection = useNativeState({ start: 0, end: 0 }); const handleValueChange = useEffectEvent((v: string) => { 'worklet'; const digits = v.replace(/\D/g, '').slice(0, 10); let formatted: string; if (digits.length === 0) { formatted = ''; } else if (digits.length <= 3) { formatted = digits; } else if (digits.length <= 6) { formatted = `(${digits.slice(0, 3)}) ${digits.slice(3)}`; } else { formatted = `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`; } if (formatted !== v) { maskedPhone.value = formatted; // Snaps to end for demo. Real masks need smarter cursor handling. selection.value = { start: formatted.length, end: formatted.length }; } }); return ( <Host matchContents> <TextField value={maskedPhone} selection={selection} keyboardOptions={{ keyboardType: 'phone' }} modifiers={[fillMaxWidth()]} onValueChange={handleValueChange}> <TextField.Placeholder> <ComposeText>(555) 123-4567</ComposeText> </TextField.Placeholder> </TextField> </Host> ); }
API
import { useNativeState } from '@expo/ui/jetpack-compose';
No API data file found, sorry!