import { useRef, useCallback, useState } from 'react'; import type { RiveViewRef } from '@rive-app/react-native'; export function useRive() { const riveRef = useRef(null); // `undefined` = view not ready yet, `null` = failed/detached — the same // convention as useRiveFile, so hooks consuming the ref (e.g. // useViewModelInstance({async: true})) stay in their loading state until the view is // actually ready instead of settling on a transient null. const [riveViewRef, setRiveViewRef] = useState< RiveViewRef | null | undefined >(undefined); const timeoutRef = useRef | null>(null); const setRef = useCallback((node: RiveViewRef | null) => { if (riveRef.current !== node) { riveRef.current = node; // Clear any existing timeout if (timeoutRef.current) { clearTimeout(timeoutRef.current); } const timeout = new Promise((_, reject) => { timeoutRef.current = setTimeout(() => { reject(new Error('Rive view ready timeout')); }, 5000); }); // TODO: Need to clear out the awaitViewReady promise if it times out // or add this timeout natively and return false Promise.race([node?.awaitViewReady(), timeout]) .then((result) => { if (result === true) { setRiveViewRef(node); } else { console.warn('Rive view ready check returned false'); setRiveViewRef(null); } }) .catch((error) => { console.warn('Failed to initialize Rive view:', error); setRiveViewRef(null); }) .finally(() => { // Clear the timeout in both success and error cases if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null; } }); } }, []); return { riveRef, riveViewRef, setHybridRef: { f: setRef, }, }; }