diff --git a/packages/bottom-tabs/src/utils/useIsKeyboardShown.tsx b/packages/bottom-tabs/src/utils/useIsKeyboardShown.tsx new file mode 100644 index 0000000000..4ab974c3ff --- /dev/null +++ b/packages/bottom-tabs/src/utils/useIsKeyboardShown.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { Keyboard, Platform } from 'react-native'; + +export default function useIsKeyboardShown() { + const [isKeyboardShown, setIsKeyboardShown] = React.useState(false); + + React.useEffect(() => { + const handleKeyboardShow = () => setIsKeyboardShown(true); + const handleKeyboardHide = () => setIsKeyboardShown(false); + + if (Platform.OS === 'ios') { + Keyboard.addListener('keyboardWillShow', handleKeyboardShow); + Keyboard.addListener('keyboardWillHide', handleKeyboardHide); + } else { + Keyboard.addListener('keyboardDidShow', handleKeyboardShow); + Keyboard.addListener('keyboardDidHide', handleKeyboardHide); + } + + return () => { + if (Platform.OS === 'ios') { + Keyboard.removeListener('keyboardWillShow', handleKeyboardShow); + Keyboard.removeListener('keyboardWillHide', handleKeyboardHide); + } else { + Keyboard.removeListener('keyboardDidShow', handleKeyboardShow); + Keyboard.removeListener('keyboardDidHide', handleKeyboardHide); + } + }; + }, []); + + return isKeyboardShown; +} diff --git a/packages/bottom-tabs/src/utils/useWindowDimensions.tsx b/packages/bottom-tabs/src/utils/useWindowDimensions.tsx new file mode 100644 index 0000000000..4d472f19ab --- /dev/null +++ b/packages/bottom-tabs/src/utils/useWindowDimensions.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import { ScaledSize, Dimensions } from 'react-native'; + +// This is similar to the new useWindowDimensions hook in react-native +// However, we have a custom implementation to support older RN versions +export default function useWindowDimensions() { + const [dimensions, setDimensions] = React.useState(() => { + // `height` and `width` maybe undefined during SSR, so we initialize them + const { height = 0, width = 0 } = Dimensions.get('window'); + + return { height, width }; + }); + + React.useEffect(() => { + const onChange = ({ window }: { window: ScaledSize }) => { + const { width, height } = window; + + setDimensions((d) => { + if (width === d.width && height === d.height) { + return d; + } + + return { width, height }; + }); + }; + + // We might have missed an update before the listener was added + // So make sure to update the dimensions + onChange({ window: Dimensions.get('window') }); + + Dimensions.addEventListener('change', onChange); + + return () => Dimensions.addEventListener('change', onChange); + }, []); + + return dimensions; +} diff --git a/packages/bottom-tabs/src/views/BottomTabBar.tsx b/packages/bottom-tabs/src/views/BottomTabBar.tsx index 734f4aadd6..296c0652ff 100644 --- a/packages/bottom-tabs/src/views/BottomTabBar.tsx +++ b/packages/bottom-tabs/src/views/BottomTabBar.tsx @@ -3,11 +3,8 @@ import { View, Animated, StyleSheet, - Keyboard, Platform, LayoutChangeEvent, - ScaledSize, - Dimensions, } from 'react-native'; import { NavigationContext, @@ -19,6 +16,8 @@ import { import { useSafeArea } from 'react-native-safe-area-context'; import BottomTabItem from './BottomTabItem'; +import useWindowDimensions from '../utils/useWindowDimensions'; +import useIsKeyboardShown from '../utils/useIsKeyboardShown'; import type { BottomTabBarProps } from '../types'; type Props = BottomTabBarProps & { @@ -57,7 +56,8 @@ export default function BottomTabBar({ const focusedDescriptor = descriptors[focusedRoute.key]; const focusedOptions = focusedDescriptor.options; - const [isKeyboardShown, setIsKeyboardShown] = React.useState(false); + const dimensions = useWindowDimensions(); + const isKeyboardShown = useIsKeyboardShown(); const shouldShowTabBar = focusedOptions.tabBarVisible !== false && @@ -91,43 +91,6 @@ export default function BottomTabBar({ } }, [shouldShowTabBar, visible]); - const [dimensions, setDimensions] = React.useState(() => { - const { height = 0, width = 0 } = Dimensions.get('window'); - - return { height, width }; - }); - - React.useEffect(() => { - const handleOrientationChange = ({ window }: { window: ScaledSize }) => { - setDimensions(window); - }; - - Dimensions.addEventListener('change', handleOrientationChange); - - const handleKeyboardShow = () => setIsKeyboardShown(true); - const handleKeyboardHide = () => setIsKeyboardShown(false); - - if (Platform.OS === 'ios') { - Keyboard.addListener('keyboardWillShow', handleKeyboardShow); - Keyboard.addListener('keyboardWillHide', handleKeyboardHide); - } else { - Keyboard.addListener('keyboardDidShow', handleKeyboardShow); - Keyboard.addListener('keyboardDidHide', handleKeyboardHide); - } - - return () => { - Dimensions.removeEventListener('change', handleOrientationChange); - - if (Platform.OS === 'ios') { - Keyboard.removeListener('keyboardWillShow', handleKeyboardShow); - Keyboard.removeListener('keyboardWillHide', handleKeyboardHide); - } else { - Keyboard.removeListener('keyboardDidShow', handleKeyboardShow); - Keyboard.removeListener('keyboardDidHide', handleKeyboardHide); - } - }; - }, []); - const [layout, setLayout] = React.useState({ height: 0, width: dimensions.width, diff --git a/packages/drawer/src/utils/useWindowDimensions.tsx b/packages/drawer/src/utils/useWindowDimensions.tsx new file mode 100644 index 0000000000..4d472f19ab --- /dev/null +++ b/packages/drawer/src/utils/useWindowDimensions.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import { ScaledSize, Dimensions } from 'react-native'; + +// This is similar to the new useWindowDimensions hook in react-native +// However, we have a custom implementation to support older RN versions +export default function useWindowDimensions() { + const [dimensions, setDimensions] = React.useState(() => { + // `height` and `width` maybe undefined during SSR, so we initialize them + const { height = 0, width = 0 } = Dimensions.get('window'); + + return { height, width }; + }); + + React.useEffect(() => { + const onChange = ({ window }: { window: ScaledSize }) => { + const { width, height } = window; + + setDimensions((d) => { + if (width === d.width && height === d.height) { + return d; + } + + return { width, height }; + }); + }; + + // We might have missed an update before the listener was added + // So make sure to update the dimensions + onChange({ window: Dimensions.get('window') }); + + Dimensions.addEventListener('change', onChange); + + return () => Dimensions.addEventListener('change', onChange); + }, []); + + return dimensions; +} diff --git a/packages/drawer/src/views/DrawerView.tsx b/packages/drawer/src/views/DrawerView.tsx index ac3fbbcfaa..3421f1c1f7 100644 --- a/packages/drawer/src/views/DrawerView.tsx +++ b/packages/drawer/src/views/DrawerView.tsx @@ -1,11 +1,9 @@ import * as React from 'react'; import { View, - Dimensions, StyleSheet, I18nManager, Platform, - ScaledSize, BackHandler, NativeEventSubscription, } from 'react-native'; @@ -25,6 +23,7 @@ import DrawerContent from './DrawerContent'; import Drawer from './Drawer'; import DrawerOpenContext from '../utils/DrawerOpenContext'; import DrawerPositionContext from '../utils/DrawerPositionContext'; +import useWindowDimensions from '../utils/useWindowDimensions'; import type { DrawerDescriptorMap, DrawerNavigationConfig, @@ -86,9 +85,7 @@ export default function DrawerView({ sceneContainerStyle, }: Props) { const [loaded, setLoaded] = React.useState([state.index]); - const [dimensions, setDimensions] = React.useState(() => - Dimensions.get('window') - ); + const dimensions = useWindowDimensions(); const { colors } = useTheme(); @@ -133,16 +130,6 @@ export default function DrawerView({ return () => subscription?.remove(); }, [handleDrawerClose, isDrawerOpen, navigation, state.key]); - React.useEffect(() => { - const updateDimensions = ({ window }: { window: ScaledSize }) => { - setDimensions(window); - }; - - Dimensions.addEventListener('change', updateDimensions); - - return () => Dimensions.removeEventListener('change', updateDimensions); - }, []); - if (!loaded.includes(state.index)) { setLoaded([...loaded, state.index]); }