Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Dedupe navigation events (push, navigate, pop, etc) (#3179)

authored by

Hailey and committed by
GitHub
ee57d747 b8afb935

+118 -25
+4 -7
src/components/Link.tsx
··· 1 1 import React from 'react' 2 2 import {GestureResponderEvent} from 'react-native' 3 - import { 4 - useLinkProps, 5 - useNavigation, 6 - StackActions, 7 - } from '@react-navigation/native' 3 + import {useLinkProps, StackActions} from '@react-navigation/native' 8 4 import {sanitizeUrl} from '@braintree/sanitize-url' 9 5 10 6 import {useInteractionState} from '#/components/hooks/useInteractionState' 11 7 import {isWeb} from '#/platform/detection' 12 8 import {useTheme, web, flatten, TextStyleProp, atoms as a} from '#/alf' 13 9 import {Button, ButtonProps} from '#/components/Button' 14 - import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types' 10 + import {AllNavigatorParams} from '#/lib/routes/types' 15 11 import { 16 12 convertBskyAppUrlIfNeeded, 17 13 isExternalUrl, ··· 21 17 import {router} from '#/routes' 22 18 import {Text, TextProps} from '#/components/Typography' 23 19 import {useOpenLink} from 'state/preferences/in-app-browser' 20 + import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped' 24 21 25 22 /** 26 23 * Only available within a `Link`, since that inherits from `Button`. ··· 74 71 }: BaseLinkProps & { 75 72 displayText: string 76 73 }) { 77 - const navigation = useNavigation<NavigationProp>() 74 + const navigation = useNavigationDeduped() 78 75 const {href} = useLinkProps<AllNavigatorParams>({ 79 76 to: 80 77 typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to,
+2 -3
src/components/Lists.tsx
··· 8 8 import {Button} from '#/components/Button' 9 9 import {Text} from '#/components/Typography' 10 10 import {StackActions} from '@react-navigation/native' 11 - import {useNavigation} from '@react-navigation/core' 12 - import {NavigationProp} from 'lib/routes/types' 13 11 import {router} from '#/routes' 12 + import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped' 14 13 15 14 export function ListFooter({ 16 15 isFetching, ··· 142 141 notFoundType?: 'page' | 'results' 143 142 onRetry?: () => Promise<unknown> 144 143 }) { 145 - const navigation = useNavigation<NavigationProp>() 144 + const navigation = useNavigationDeduped() 146 145 const t = useTheme() 147 146 const {gtMobile, gtTablet} = useBreakpoints() 148 147
+17
src/lib/hooks/useDedupe.ts
··· 1 + import React from 'react' 2 + 3 + export const useDedupe = () => { 4 + const canDo = React.useRef(true) 5 + 6 + return React.useRef((cb: () => unknown) => { 7 + if (canDo.current) { 8 + canDo.current = false 9 + setTimeout(() => { 10 + canDo.current = true 11 + }, 250) 12 + cb() 13 + return true 14 + } 15 + return false 16 + }).current 17 + }
+80
src/lib/hooks/useNavigationDeduped.ts
··· 1 + import React from 'react' 2 + import {useNavigation} from '@react-navigation/core' 3 + import {AllNavigatorParams, NavigationProp} from 'lib/routes/types' 4 + import type {NavigationAction} from '@react-navigation/routers' 5 + import {NavigationState} from '@react-navigation/native' 6 + import {useDedupe} from 'lib/hooks/useDedupe' 7 + 8 + export type DebouncedNavigationProp = Pick< 9 + NavigationProp, 10 + | 'popToTop' 11 + | 'push' 12 + | 'navigate' 13 + | 'canGoBack' 14 + | 'replace' 15 + | 'dispatch' 16 + | 'goBack' 17 + | 'getState' 18 + > 19 + 20 + export function useNavigationDeduped() { 21 + const navigation = useNavigation<NavigationProp>() 22 + const dedupe = useDedupe() 23 + 24 + return React.useMemo( 25 + (): DebouncedNavigationProp => ({ 26 + // Types from @react-navigation/routers/lib/typescript/src/StackRouter.ts 27 + push: <RouteName extends keyof AllNavigatorParams>( 28 + ...args: undefined extends AllNavigatorParams[RouteName] 29 + ? 30 + | [screen: RouteName] 31 + | [screen: RouteName, params: AllNavigatorParams[RouteName]] 32 + : [screen: RouteName, params: AllNavigatorParams[RouteName]] 33 + ) => { 34 + dedupe(() => navigation.push(...args)) 35 + }, 36 + // Types from @react-navigation/core/src/types.tsx 37 + navigate: <RouteName extends keyof AllNavigatorParams>( 38 + ...args: RouteName extends unknown 39 + ? undefined extends AllNavigatorParams[RouteName] 40 + ? 41 + | [screen: RouteName] 42 + | [screen: RouteName, params: AllNavigatorParams[RouteName]] 43 + : [screen: RouteName, params: AllNavigatorParams[RouteName]] 44 + : never 45 + ) => { 46 + dedupe(() => navigation.navigate(...args)) 47 + }, 48 + // Types from @react-navigation/routers/lib/typescript/src/StackRouter.ts 49 + replace: <RouteName extends keyof AllNavigatorParams>( 50 + ...args: undefined extends AllNavigatorParams[RouteName] 51 + ? 52 + | [screen: RouteName] 53 + | [screen: RouteName, params: AllNavigatorParams[RouteName]] 54 + : [screen: RouteName, params: AllNavigatorParams[RouteName]] 55 + ) => { 56 + dedupe(() => navigation.replace(...args)) 57 + }, 58 + dispatch: ( 59 + action: 60 + | NavigationAction 61 + | ((state: NavigationState) => NavigationAction), 62 + ) => { 63 + dedupe(() => navigation.dispatch(action)) 64 + }, 65 + popToTop: () => { 66 + dedupe(() => navigation.popToTop()) 67 + }, 68 + goBack: () => { 69 + dedupe(() => navigation.goBack()) 70 + }, 71 + canGoBack: () => { 72 + return navigation.canGoBack() 73 + }, 74 + getState: () => { 75 + return navigation.getState() 76 + }, 77 + }), 78 + [dedupe, navigation], 79 + ) 80 + }
+2 -3
src/view/com/feeds/FeedSourceCard.tsx
··· 6 6 import {usePalette} from 'lib/hooks/usePalette' 7 7 import {s} from 'lib/styles' 8 8 import {UserAvatar} from '../util/UserAvatar' 9 - import {useNavigation} from '@react-navigation/native' 10 - import {NavigationProp} from 'lib/routes/types' 11 9 import {pluralize} from 'lib/strings/helpers' 12 10 import {AtUri} from '@atproto/api' 13 11 import * as Toast from 'view/com/util/Toast' ··· 26 24 import {useFeedSourceInfoQuery, FeedSourceInfo} from '#/state/queries/feed' 27 25 import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 28 26 import {useTheme} from '#/alf' 27 + import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped' 29 28 30 29 export function FeedSourceCard({ 31 30 feedUri, ··· 86 85 const t = useTheme() 87 86 const pal = usePalette('default') 88 87 const {_} = useLingui() 89 - const navigation = useNavigation<NavigationProp>() 88 + const navigation = useNavigationDeduped() 90 89 const {openModal} = useModalControls() 91 90 92 91 const {isPending: isSavePending, mutateAsync: saveFeed} =
+8 -9
src/view/com/util/Link.tsx
··· 11 11 TouchableWithoutFeedback, 12 12 TouchableOpacity, 13 13 } from 'react-native' 14 - import { 15 - useLinkProps, 16 - useNavigation, 17 - StackActions, 18 - } from '@react-navigation/native' 14 + import {useLinkProps, StackActions} from '@react-navigation/native' 19 15 import {Text} from './text/Text' 20 16 import {TypographyVariant} from 'lib/ThemeContext' 21 - import {NavigationProp} from 'lib/routes/types' 22 17 import {router} from '../../../routes' 23 18 import { 24 19 convertBskyAppUrlIfNeeded, ··· 32 27 import {useModalControls} from '#/state/modals' 33 28 import {useOpenLink} from '#/state/preferences/in-app-browser' 34 29 import {WebAuxClickWrapper} from 'view/com/util/WebAuxClickWrapper' 30 + import { 31 + DebouncedNavigationProp, 32 + useNavigationDeduped, 33 + } from 'lib/hooks/useNavigationDeduped' 35 34 36 35 type Event = 37 36 | React.MouseEvent<HTMLAnchorElement, MouseEvent> ··· 65 64 ...props 66 65 }: Props) { 67 66 const {closeModal} = useModalControls() 68 - const navigation = useNavigation<NavigationProp>() 67 + const navigation = useNavigationDeduped() 69 68 const anchorHref = asAnchor ? sanitizeUrl(href) : undefined 70 69 const openLink = useOpenLink() 71 70 ··· 176 175 navigationAction?: 'push' | 'replace' | 'navigate' 177 176 } & TextProps) { 178 177 const {...props} = useLinkProps({to: sanitizeUrl(href)}) 179 - const navigation = useNavigation<NavigationProp>() 178 + const navigation = useNavigationDeduped() 180 179 const {openModal, closeModal} = useModalControls() 181 180 const openLink = useOpenLink() 182 181 ··· 335 334 // -prf 336 335 function onPressInner( 337 336 closeModal = () => {}, 338 - navigation: NavigationProp, 337 + navigation: DebouncedNavigationProp, 339 338 href: string, 340 339 navigationAction: 'push' | 'replace' | 'navigate' = 'push', 341 340 openLink: (href: string) => void,
+5 -3
src/view/shell/bottom-bar/BottomBar.tsx
··· 36 36 import {s} from 'lib/styles' 37 37 import {Logo} from '#/view/icons/Logo' 38 38 import {Logotype} from '#/view/icons/Logotype' 39 + import {useDedupe} from 'lib/hooks/useDedupe' 39 40 40 41 type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds' 41 42 ··· 54 55 const {data: profile} = useProfileQuery({did: currentAccount?.did}) 55 56 const {requestSwitchToAccount} = useLoggedOutViewControls() 56 57 const closeAllActiveElements = useCloseAllActiveElements() 58 + const dedupe = useDedupe() 57 59 58 60 const showSignIn = React.useCallback(() => { 59 61 closeAllActiveElements() ··· 74 76 if (tabState === TabState.InsideAtRoot) { 75 77 emitSoftReset() 76 78 } else if (tabState === TabState.Inside) { 77 - navigation.dispatch(StackActions.popToTop()) 79 + dedupe(() => navigation.dispatch(StackActions.popToTop())) 78 80 } else { 79 - navigation.navigate(`${tab}Tab`) 81 + dedupe(() => navigation.navigate(`${tab}Tab`)) 80 82 } 81 83 }, 82 - [track, navigation], 84 + [track, navigation, dedupe], 83 85 ) 84 86 const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab]) 85 87 const onPressSearch = React.useCallback(