import { requireNativeModule } from 'expo'; import type { EventSubscription } from 'expo-modules-core'; import { useCallback, useEffect, useState } from 'react'; import type { ExpoBrownfieldStateModuleSpec, KeyRecreatedEvent, SharedStateChangeEvent, } from './ExpoBrownfieldStateModule.types'; const ExpoBrownfieldStateModule = requireNativeModule( 'ExpoBrownfieldStateModule' ); const sharedObjectCache = new Map(); // SECTION: Shared State API function getSharedObject(key: string): any { if (!sharedObjectCache.has(key)) { sharedObjectCache.set(key, ExpoBrownfieldStateModule.getSharedState(key)); } return sharedObjectCache.get(key); } /** * Gets the value of shared state for a given key. * * @param key The key to get the value for. */ export function getSharedStateValue(key: string): T | undefined { const state = getSharedObject(key); const value = state?.get(); return value === null ? undefined : (value as T); } /** * Sets the value of shared state for a given key. * * @param key The key to set the value for. * @param value The value to be set. */ export function setSharedStateValue(key: string, value: T): void { const state = getSharedObject(key); state.set(value); } /** * Deletes the shared state for a given key. * * @param key The key to delete the shared state for. */ export function deleteSharedState(key: string): void { ExpoBrownfieldStateModule.deleteSharedState(key); sharedObjectCache.delete(key); } /** * Adds a listener for changes to the shared state for a given key. * * @param key The key to add the listener for. * @param callback The callback to be called when the shared state changes. * @returns A subscription object that can be used to remove the listener. */ export function addSharedStateListener( key: string, callback: (event: SharedStateChangeEvent | undefined) => void ): EventSubscription { const state = getSharedObject(key); const subscription = state.addListener( 'change', (event: SharedStateChangeEvent | undefined) => { callback(event); } ); return { remove: () => subscription.remove(), }; } /** * Hook to observe and set the value of shared state for a given key. * Provides a synchronous API similar to `useState`. * * @param key The key to get the value for. * @param initialValue The initial value to be used if the shared state is not set. * @returns A tuple containing the value and a function to set the value. */ export function useSharedState( key: string, initialValue?: T ): [T | undefined, (value: T | ((prev: T | undefined) => T)) => void] { const state = getSharedObject(key); const [value, setValue] = useState(() => { const currentValue = state.get(); if (currentValue === null || currentValue === undefined) { if (initialValue !== undefined) { state.set(initialValue); return initialValue; } return undefined; } return currentValue as T; }); useEffect(() => { let subscription = state.addListener( 'change', (event: Record | undefined) => { setValue(event?.['value']); } ); const keyRecreatedSubscription = ExpoBrownfieldStateModule.addListener( 'onKeyRecreated', (event: KeyRecreatedEvent) => { if (event.key === key) { const newState = ExpoBrownfieldStateModule.getSharedState(key); sharedObjectCache.set(key, newState); subscription.remove(); subscription = newState.addListener( 'change', (event: Record | undefined) => { setValue(event?.['value']); } ); setValue(getSharedStateValue(key)); } } ); return () => { subscription.remove(); keyRecreatedSubscription.remove(); }; }, [state]); const setSharedValue = useCallback( (newValue: T | ((prev: T | undefined) => T)) => { const valueToSet = typeof newValue === 'function' ? (newValue as (prev: T | undefined) => T)(value) : newValue; getSharedObject(key).set(valueToSet); }, [key, value] ); return [value, setSharedValue]; } // END SECTION: Shared State API