one
Version:
One is a React framework for web, iOS, and Android apps.
44 lines (38 loc) • 1.84 kB
text/typescript
import { useCallback, useRef, useSyncExternalStore } from 'react'
import { createSyncState, isSyncState, type SyncState } from './syncStore'
// observable state shared between JavaScript and native views, matching Expo's
// useNativeState surface: one handle feeds any number of controlled props, so
// every bound view converges on the same value. the handle is created once per
// hook instance; writes go straight to the sync store and notify subscribers
// in the same frame, never waiting for a React render. the component itself
// subscribes so `.value` reads in render stay live.
export type NativeState<T> = SyncState<T>
export function useNativeState<T>(initial: T): NativeState<T> {
const ref = useRef<SyncState<T> | null>(null)
if (ref.current === null) {
ref.current = createSyncState(initial)
}
const state = ref.current
useSyncExternalStore(state.subscribe, state.getSnapshot, state.getSnapshot)
return state
}
// the handle behind a sync value prop, or null for a plain scalar. generated
// adapters use this to bind native views to the caller's entry.
export function syncHandleOf<T>(value: T | SyncState<T>): SyncState<T> | null {
return isSyncState(value) ? (value as SyncState<T>) : null
}
// resolves a sync value prop to the plain scalar the native view carries. a
// handle subscribes, so writes from any bound view re-render this one; a plain
// scalar passes through untouched.
export function useSyncValue<T>(value: T | SyncState<T>): T {
const handle = syncHandleOf(value)
const subscribe = useCallback(
(notify: () => void) => (handle ? handle.subscribe(notify) : () => {}),
[handle]
)
const getSnapshot = useCallback(
() => (handle ? handle.getSnapshot() : (value as T)),
[handle, value]
)
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
}