diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/Link.tsx | 11 | ||||
-rw-r--r-- | src/components/Lists.tsx | 5 | ||||
-rw-r--r-- | src/lib/hooks/useDedupe.ts | 17 | ||||
-rw-r--r-- | src/lib/hooks/useNavigationDeduped.ts | 80 | ||||
-rw-r--r-- | src/view/com/feeds/FeedSourceCard.tsx | 5 | ||||
-rw-r--r-- | src/view/com/util/Link.tsx | 17 | ||||
-rw-r--r-- | src/view/shell/bottom-bar/BottomBar.tsx | 8 |
7 files changed, 118 insertions, 25 deletions
diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 8c963909b..ff72a08ce 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -1,17 +1,13 @@ import React from 'react' import {GestureResponderEvent} from 'react-native' -import { - useLinkProps, - useNavigation, - StackActions, -} from '@react-navigation/native' +import {useLinkProps, StackActions} from '@react-navigation/native' import {sanitizeUrl} from '@braintree/sanitize-url' import {useInteractionState} from '#/components/hooks/useInteractionState' import {isWeb} from '#/platform/detection' import {useTheme, web, flatten, TextStyleProp, atoms as a} from '#/alf' import {Button, ButtonProps} from '#/components/Button' -import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types' +import {AllNavigatorParams} from '#/lib/routes/types' import { convertBskyAppUrlIfNeeded, isExternalUrl, @@ -21,6 +17,7 @@ import {useModalControls} from '#/state/modals' import {router} from '#/routes' import {Text, TextProps} from '#/components/Typography' import {useOpenLink} from 'state/preferences/in-app-browser' +import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped' /** * Only available within a `Link`, since that inherits from `Button`. @@ -74,7 +71,7 @@ export function useLink({ }: BaseLinkProps & { displayText: string }) { - const navigation = useNavigation<NavigationProp>() + const navigation = useNavigationDeduped() const {href} = useLinkProps<AllNavigatorParams>({ to: typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to, diff --git a/src/components/Lists.tsx b/src/components/Lists.tsx index 58aa74b38..bb0d24797 100644 --- a/src/components/Lists.tsx +++ b/src/components/Lists.tsx @@ -8,9 +8,8 @@ import {cleanError} from 'lib/strings/errors' import {Button} from '#/components/Button' import {Text} from '#/components/Typography' import {StackActions} from '@react-navigation/native' -import {useNavigation} from '@react-navigation/core' -import {NavigationProp} from 'lib/routes/types' import {router} from '#/routes' +import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped' export function ListFooter({ isFetching, @@ -142,7 +141,7 @@ export function ListMaybePlaceholder({ notFoundType?: 'page' | 'results' onRetry?: () => Promise<unknown> }) { - const navigation = useNavigation<NavigationProp>() + const navigation = useNavigationDeduped() const t = useTheme() const {gtMobile, gtTablet} = useBreakpoints() diff --git a/src/lib/hooks/useDedupe.ts b/src/lib/hooks/useDedupe.ts new file mode 100644 index 000000000..d9432cb2c --- /dev/null +++ b/src/lib/hooks/useDedupe.ts @@ -0,0 +1,17 @@ +import React from 'react' + +export const useDedupe = () => { + const canDo = React.useRef(true) + + return React.useRef((cb: () => unknown) => { + if (canDo.current) { + canDo.current = false + setTimeout(() => { + canDo.current = true + }, 250) + cb() + return true + } + return false + }).current +} diff --git a/src/lib/hooks/useNavigationDeduped.ts b/src/lib/hooks/useNavigationDeduped.ts new file mode 100644 index 000000000..d913f7f3d --- /dev/null +++ b/src/lib/hooks/useNavigationDeduped.ts @@ -0,0 +1,80 @@ +import React from 'react' +import {useNavigation} from '@react-navigation/core' +import {AllNavigatorParams, NavigationProp} from 'lib/routes/types' +import type {NavigationAction} from '@react-navigation/routers' +import {NavigationState} from '@react-navigation/native' +import {useDedupe} from 'lib/hooks/useDedupe' + +export type DebouncedNavigationProp = Pick< + NavigationProp, + | 'popToTop' + | 'push' + | 'navigate' + | 'canGoBack' + | 'replace' + | 'dispatch' + | 'goBack' + | 'getState' +> + +export function useNavigationDeduped() { + const navigation = useNavigation<NavigationProp>() + const dedupe = useDedupe() + + return React.useMemo( + (): DebouncedNavigationProp => ({ + // Types from @react-navigation/routers/lib/typescript/src/StackRouter.ts + push: <RouteName extends keyof AllNavigatorParams>( + ...args: undefined extends AllNavigatorParams[RouteName] + ? + | [screen: RouteName] + | [screen: RouteName, params: AllNavigatorParams[RouteName]] + : [screen: RouteName, params: AllNavigatorParams[RouteName]] + ) => { + dedupe(() => navigation.push(...args)) + }, + // Types from @react-navigation/core/src/types.tsx + navigate: <RouteName extends keyof AllNavigatorParams>( + ...args: RouteName extends unknown + ? undefined extends AllNavigatorParams[RouteName] + ? + | [screen: RouteName] + | [screen: RouteName, params: AllNavigatorParams[RouteName]] + : [screen: RouteName, params: AllNavigatorParams[RouteName]] + : never + ) => { + dedupe(() => navigation.navigate(...args)) + }, + // Types from @react-navigation/routers/lib/typescript/src/StackRouter.ts + replace: <RouteName extends keyof AllNavigatorParams>( + ...args: undefined extends AllNavigatorParams[RouteName] + ? + | [screen: RouteName] + | [screen: RouteName, params: AllNavigatorParams[RouteName]] + : [screen: RouteName, params: AllNavigatorParams[RouteName]] + ) => { + dedupe(() => navigation.replace(...args)) + }, + dispatch: ( + action: + | NavigationAction + | ((state: NavigationState) => NavigationAction), + ) => { + dedupe(() => navigation.dispatch(action)) + }, + popToTop: () => { + dedupe(() => navigation.popToTop()) + }, + goBack: () => { + dedupe(() => navigation.goBack()) + }, + canGoBack: () => { + return navigation.canGoBack() + }, + getState: () => { + return navigation.getState() + }, + }), + [dedupe, navigation], + ) +} diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx index 9bd7238df..4af62f6f6 100644 --- a/src/view/com/feeds/FeedSourceCard.tsx +++ b/src/view/com/feeds/FeedSourceCard.tsx @@ -6,8 +6,6 @@ import {RichText} from '#/components/RichText' import {usePalette} from 'lib/hooks/usePalette' import {s} from 'lib/styles' import {UserAvatar} from '../util/UserAvatar' -import {useNavigation} from '@react-navigation/native' -import {NavigationProp} from 'lib/routes/types' import {pluralize} from 'lib/strings/helpers' import {AtUri} from '@atproto/api' import * as Toast from 'view/com/util/Toast' @@ -26,6 +24,7 @@ import { import {useFeedSourceInfoQuery, FeedSourceInfo} from '#/state/queries/feed' import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {useTheme} from '#/alf' +import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped' export function FeedSourceCard({ feedUri, @@ -86,7 +85,7 @@ export function FeedSourceCardLoaded({ const t = useTheme() const pal = usePalette('default') const {_} = useLingui() - const navigation = useNavigation<NavigationProp>() + const navigation = useNavigationDeduped() const {openModal} = useModalControls() const {isPending: isSavePending, mutateAsync: saveFeed} = diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index e50fb7f09..f45622488 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -11,14 +11,9 @@ import { TouchableWithoutFeedback, TouchableOpacity, } from 'react-native' -import { - useLinkProps, - useNavigation, - StackActions, -} from '@react-navigation/native' +import {useLinkProps, StackActions} from '@react-navigation/native' import {Text} from './text/Text' import {TypographyVariant} from 'lib/ThemeContext' -import {NavigationProp} from 'lib/routes/types' import {router} from '../../../routes' import { convertBskyAppUrlIfNeeded, @@ -32,6 +27,10 @@ import FixedTouchableHighlight from '../pager/FixedTouchableHighlight' import {useModalControls} from '#/state/modals' import {useOpenLink} from '#/state/preferences/in-app-browser' import {WebAuxClickWrapper} from 'view/com/util/WebAuxClickWrapper' +import { + DebouncedNavigationProp, + useNavigationDeduped, +} from 'lib/hooks/useNavigationDeduped' type Event = | React.MouseEvent<HTMLAnchorElement, MouseEvent> @@ -65,7 +64,7 @@ export const Link = memo(function Link({ ...props }: Props) { const {closeModal} = useModalControls() - const navigation = useNavigation<NavigationProp>() + const navigation = useNavigationDeduped() const anchorHref = asAnchor ? sanitizeUrl(href) : undefined const openLink = useOpenLink() @@ -176,7 +175,7 @@ export const TextLink = memo(function TextLink({ navigationAction?: 'push' | 'replace' | 'navigate' } & TextProps) { const {...props} = useLinkProps({to: sanitizeUrl(href)}) - const navigation = useNavigation<NavigationProp>() + const navigation = useNavigationDeduped() const {openModal, closeModal} = useModalControls() const openLink = useOpenLink() @@ -335,7 +334,7 @@ const EXEMPT_PATHS = ['/robots.txt', '/security.txt', '/.well-known/'] // -prf function onPressInner( closeModal = () => {}, - navigation: NavigationProp, + navigation: DebouncedNavigationProp, href: string, navigationAction: 'push' | 'replace' | 'navigate' = 'push', openLink: (href: string) => void, diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx index 1ab3334fa..115faa296 100644 --- a/src/view/shell/bottom-bar/BottomBar.tsx +++ b/src/view/shell/bottom-bar/BottomBar.tsx @@ -36,6 +36,7 @@ import {Button} from '#/view/com/util/forms/Button' import {s} from 'lib/styles' import {Logo} from '#/view/icons/Logo' import {Logotype} from '#/view/icons/Logotype' +import {useDedupe} from 'lib/hooks/useDedupe' type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds' @@ -54,6 +55,7 @@ export function BottomBar({navigation}: BottomTabBarProps) { const {data: profile} = useProfileQuery({did: currentAccount?.did}) const {requestSwitchToAccount} = useLoggedOutViewControls() const closeAllActiveElements = useCloseAllActiveElements() + const dedupe = useDedupe() const showSignIn = React.useCallback(() => { closeAllActiveElements() @@ -74,12 +76,12 @@ export function BottomBar({navigation}: BottomTabBarProps) { if (tabState === TabState.InsideAtRoot) { emitSoftReset() } else if (tabState === TabState.Inside) { - navigation.dispatch(StackActions.popToTop()) + dedupe(() => navigation.dispatch(StackActions.popToTop())) } else { - navigation.navigate(`${tab}Tab`) + dedupe(() => navigation.navigate(`${tab}Tab`)) } }, - [track, navigation], + [track, navigation, dedupe], ) const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab]) const onPressSearch = React.useCallback( |