Reference version

This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

useNativeState

A React hook that creates observable state shared between JavaScript and native SwiftUI views.

iOS
tvOS
Included in Expo Go

useNativeState returns an ObservableState that maps to a SwiftUI ObservableObject on the native side, so reads and writes to .value are observed directly by SwiftUI without going through the React render cycle. This lets you update the native view synchronously from a worklet on the UI thread.

Installation

Terminal
- npx expo install @expo/ui
- yarn expo install @expo/ui
- pnpm expo install @expo/ui
- bun expo install @expo/ui

If you are installing this in an existing React Native app, make sure to install expo in your project.

Usage

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.

WorkletPhoneMaskExample.tsx
import { Host, TextField, useNativeState } from '@expo/ui/swift-ui'; import { keyboardType } from '@expo/ui/swift-ui/modifiers'; import { useCallback } from 'react'; export default function WorkletPhoneMaskExample() { const maskedPhone = useNativeState(''); const selection = useNativeState({ start: 0, end: 0 }); const handleTextChange = useCallback( (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, }; } }, [maskedPhone, selection] ); return ( <Host matchContents> <TextField text={maskedPhone} selection={selection} placeholder="(555) 123-4567" modifiers={[keyboardType('phone-pad')]} onTextChange={handleTextChange} /> </Host> ); }

Worklet callbacks

Modifiers that report changes, such as onHingeChange, take either a plain function, which runs on the JavaScript thread, or a callback from useWorkletCallback, which runs synchronously on the UI thread. Pair it with useNativeState to update a view on every change without a React render.

WorkletHingeExample.tsx
import { Host, Text, useNativeState, useWorkletCallback, VStack, } from '@expo/ui/swift-ui'; import { onHingeChange } from '@expo/ui/swift-ui/modifiers'; export default function WorkletHingeExample() { const isFullyOpen = useNativeState(false); const onHinge = useWorkletCallback((_, newContext) => { 'worklet'; isFullyOpen.value = newContext.hinge?.status === 'fullyOpen'; }); return ( <Host matchContents> <VStack modifiers={[onHingeChange(onHinge)]}> <Text>Fold the device</Text> </VStack> </Host> ); }

API

import { useNativeState } from '@expo/ui/swift-ui';

Hooks

useNativeState(initialValue)

iOS
tvOS
ParameterType
initialValueT

Creates an observable native state that is automatically cleaned up when the component unmounts. initialValue is captured once on the first render

Returns:
ObservableState<T>

useWorkletCallback(callback)

iOS
tvOS
ParameterType
callbackT

Wraps a worklet function so native code can run it synchronously on the UI thread, with no JS-thread round trip. Pass the result to a modifier that accepts worklet callbacks, such as onHingeChange. The callback must start with the 'worklet' directive, and react-native-worklets must be installed.

The native callback is updated when callback changes and released when the component unmounts, so call it at the top of your component like any hook.

Returns:
WorkletCallback<T>

Example

const angle = useNativeState(180); const onHinge = useWorkletCallback((_, newContext) => { 'worklet'; angle.value = newContext.hinge?.angle ?? 180; }); <VStack modifiers={[onHingeChange(onHinge)]} />

Types

ObservableState

iOS
tvOS

Observable state shared between JavaScript and native views (Jetpack Compose on Android and SwiftUI on iOS).

Type: SharedObject extended by:

PropertyTypeDescription
onChange[listener] | null

A single listener invoked on the native UI runtime whenever the value changes (after iOS didSet and Android's setter). Assigning replaces the previous listener; assign null to clear. The initial value does not fire onChange.

The callback must be a worklet so it can run synchronously on the UI thread. Attach it inside useEffect and clear it in the cleanup so the listener lifecycle matches the component lifecycle.

Example

const state = useNativeState(0); useEffect(() => { state.onChange = (value) => { 'worklet'; console.log('changed to', value); }; }, []);
valueT

The current value.

Writes from a UI worklet are synchronous and immediately readable. Writes from the JS thread are scheduled to the UI thread asynchronously, the new value is not readable until the update has been applied. Prefer writing from a worklet when you need synchronous updates

get() => T

Reads the current value. A React Compiler compliant alternative to reading .value

set(value: T) => void

Writes a new value. A React Compiler-compliant alternative to assigning .value

WorkletCallback

iOS
tvOS

A worklet function wrapped for native code, returned by useWorkletCallback. Pass it to a modifier or prop that accepts worklet callbacks, which then runs it synchronously on the UI thread.

Type: SharedObject extended by:

PropertyTypeDescription