import * as SplashModule from 'expo-splash-screen' import { nanoid } from 'nanoid/non-secure' import * as React from 'react' import { Platform } from 'react-native' import { useDeprecated } from '../useDeprecated' const globalStack: string[] = [] /** * A stack based component for keeping the splash screen visible. * Useful for stacked requests that need to be completed before the app is ready. * After all instances have been unmounted, the splash screen will be hidden. * * @example * ```tsx * function App() { * const [isLoading, setIsLoading] = React.useState(true); * * if (isLoading) { * return * } * * return Ready! * } * ``` */ export function SplashScreen() { useGlobalSplash() useDeprecated( 'The component is deprecated. Use `SplashScreen.preventAutoHideAsync()` and `SplashScreen.hideAsync` from `router` instead.' ) return null } function useGlobalSplash() { const stack = React.useRef(null) React.useEffect(() => { // Create a stack entry on component mount stack.current = SplashScreen._pushEntry() return () => { if (stack.current) { // Update on component unmount SplashScreen._popEntry(stack.current) } } }, []) } SplashScreen.hideAsync = () => { forceHideAsync() globalStack.length = 0 } let _userControlledAutoHideEnabled = false let _preventAutoHideAsyncInvoked = false // Expo Router uses this internal method to ensure that we can detect if the user // has explicitly opted into preventing the splash screen from hiding. This means // they will also explicitly hide it. If they don't, we will hide it for them after // the navigation render completes. export const _internal_preventAutoHideAsync = () => { // Memoize, this should only be called once. if (_preventAutoHideAsyncInvoked) { return } _preventAutoHideAsyncInvoked = true // Append error handling to ensure any uncaught exceptions result in the splash screen being hidden. if (Platform.OS !== 'web' && ErrorUtils?.getGlobalHandler) { const originalHandler = ErrorUtils.getGlobalHandler() ErrorUtils.setGlobalHandler((error, isFatal) => { SplashScreen.hideAsync() originalHandler(error, isFatal) }) } SplashModule.preventAutoHideAsync() } export const _internal_maybeHideAsync = () => { // If the user has explicitly opted into preventing the splash screen from hiding, // we should not hide it for them. This is often used for animated splash screens. if (_userControlledAutoHideEnabled) { return } SplashScreen.hideAsync() } async function forceHideAsync() { return SplashModule.hideAsync().catch((error: any) => { // Hide this very unfortunate error. if ( // Only throw the error is something unexpected happened. _preventAutoHideAsyncInvoked && error.message.includes('No native splash screen registered for ') ) { return } throw error }) } SplashScreen.preventAutoHideAsync = () => { _userControlledAutoHideEnabled = true _internal_preventAutoHideAsync() } SplashScreen._pushEntry = (): any => { const entry = nanoid() globalStack.push(entry) SplashScreen.preventAutoHideAsync() return entry } SplashScreen._popEntry = (entry: string) => { const index = globalStack.indexOf(entry) if (index !== -1) { globalStack.splice(index, 1) } if (globalStack.length === 0) { SplashScreen.hideAsync() } } // TODO: Add some detection for if the splash screen is visible