diff --git a/packages/expo-router/src/hooks.ts b/packages/expo-router/src/hooks.ts index 96758a5d..3aa0ca94 100644 --- a/packages/expo-router/src/hooks.ts +++ b/packages/expo-router/src/hooks.ts @@ -11,6 +11,7 @@ import { useStoreRouteInfo, } from "./global-state/router-store"; import { Router } from "./types"; +import { useDeprecated } from "./useDeprecated"; type SearchParams = Record; @@ -28,7 +29,7 @@ export function useRootNavigation() { // Wraps useLinkTo to provide an API which is similar to the Link component. export function useLink() { - console.warn("`useLink()` is deprecated in favor of `useRouter()`"); + useDeprecated("`useLink()` is deprecated in favor of `useRouter()`"); return useRouter(); } diff --git a/packages/expo-router/src/useDeprecated.ts b/packages/expo-router/src/useDeprecated.ts new file mode 100644 index 00000000..20e63bc4 --- /dev/null +++ b/packages/expo-router/src/useDeprecated.ts @@ -0,0 +1,35 @@ +import { useLayoutEffect } from "react"; +import { Platform } from "react-native"; + +// Node environment may render in multiple processes causing the warning to log mutiple times +// Hence we skip the warning in these environments. +const canWarn = Platform.select({ + native: process.env.NODE_ENV !== "production", + default: + process.env.NODE_ENV !== "production" && typeof window !== "undefined", +}); + +const warned = new Set(); + +export function useWarnOnce( + message: string, + guard: unknown = true, + key = message +) { + // useLayoutEffect typically doesn't run in node environments. + // Combined with skipWarn, this should prevent unwanted warnings + useLayoutEffect(() => { + if (guard && canWarn && !warned.has(key)) { + warned.add(key); + console.warn(message); + } + }, [guard]); +} + +export function useDeprecated( + message: string, + guard: unknown = true, + key = message +) { + return useWarnOnce(key, guard, `Expo Router: ${message}`); +} diff --git a/packages/expo-router/src/views/Screen.tsx b/packages/expo-router/src/views/Screen.tsx index 6771a947..17958305 100644 --- a/packages/expo-router/src/views/Screen.tsx +++ b/packages/expo-router/src/views/Screen.tsx @@ -1,5 +1,6 @@ import React from "react"; +import { useDeprecated } from "../useDeprecated"; import { useNavigation } from "../useNavigation"; export type ScreenProps< @@ -36,6 +37,14 @@ export function Screen({ navigation.setOptions(options ?? {}); }, [navigation, options]); + if (process.env.NODE_ENV === "development") { + // eslint-disable-next-line react-hooks/rules-of-hooks + useDeprecated( + "The `redirect` prop on is deprecated and will be removed. Please use `router.redirect` instead", + redirect + ); + } + if (process.env.NODE_ENV !== "production") { // eslint-disable-next-line react-hooks/rules-of-hooks React.useEffect(() => { diff --git a/packages/expo-router/src/views/Splash.tsx b/packages/expo-router/src/views/Splash.tsx index adb5fe31..808a973b 100644 --- a/packages/expo-router/src/views/Splash.tsx +++ b/packages/expo-router/src/views/Splash.tsx @@ -3,6 +3,8 @@ import { nanoid } from "nanoid/non-secure"; import * as React from "react"; import { Platform } from "react-native"; +import { useDeprecated } from "../useDeprecated"; + const globalStack: string[] = []; /** @@ -25,11 +27,9 @@ const globalStack: string[] = []; */ export function SplashScreen() { useGlobalSplash(); - React.useEffect(() => { - console.warn( - "The component is deprecated. Use `SplashScreen.preventAutoHideAsync()` and `SplashScreen.hideAsync` from `expo-router` instead." - ); - }, []); + useDeprecated( + "The component is deprecated. Use `SplashScreen.preventAutoHideAsync()` and `SplashScreen.hideAsync` from `expo-router` instead." + ); return null; }