Skip to content

Commit 74dbb8d

Browse files
authored
chore(predict): opt in useNavigation to AppNavigationProp (Phase 4) (#33458)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> <!-- mms-check directive vocabulary — read by .github/scripts/shared/pr-template-checks.ts at module load to build the validation plan. Directives are invisible in rendered markdown and must NOT be removed or edited without updating the validator registry. type=text Section must contain non-placeholder prose. type=changelog Section must have a valid CHANGELOG entry: line. type=issue-link Section must have a Fixes:/Closes:/Refs: line with a value. type=manual-testing Section must have real testing steps or an explicit N/A. type=screenshot Section must have evidence (image/URL) or an explicit N/A. type=checklist Section must have all checkboxes consciously checked. required=true|false Whether a missing/invalid section runs the validator at all. blocking=true|false Whether a failure of this check fails the CI workflow. Default: false — failures are shown as warnings in the sticky comment but do not block the PR. Sections without a directive are checked for structural presence only. --> ## **Description** <!-- mms-check: type=text required=true --> <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> **Phase 4 of the incremental React Navigation typing migration — Predict feature.** Predict production call sites still used untyped `useNavigation()` (or the feature-local `NavigationProp<PredictNavigationParamList>` / `NativeStackNavigationProp<PredictNavigationParamList>`), so they were checked only against the Predict param list (or the loose global `ReactNavigation.RootParamList`) and could not enforce route names / params against the app-wide `RootStackParamList`. This PR opts Predict into the strict path via `useNavigation<AppNavigationProp>()` (and `AppStackNavigationProp` where stack-only APIs are needed). This is effectively **types-only** ### What changed 1. **Opt-in across Predict (~51 production files)** - `useNavigation()` / `useNavigation<NavigationProp<PredictNavigationParamList>>()` → `useNavigation<AppNavigationProp>()` - Added `import type { AppNavigationProp }` from `app/core/NavigationService/types` - Covers feed / home sections, market cards, market details, positions, transactions, buy/sell previews, buy-with-any-token, add-funds, world-cup modules, GTM modal, and hooks 2. **Guard prop retype** (`app/components/UI/Predict/hooks/usePredictActionGuard.ts`) - `navigation: NavigationProp<PredictNavigationParamList>` → `navigation: AppNavigationProp`, so callers passing the root-typed navigation no longer clash with the guard 3. **Stack-only APIs use `AppStackNavigationProp`** (`app/components/UI/Predict/views/PredictBuyWithAnyToken/hooks/usePredictBuyActions.ts`) - `useNavigation<NativeStackNavigationProp<PredictNavigationParamList>>()` → `useNavigation<AppStackNavigationProp>()` (needs native-stack `transitionEnd` event + `StackActions.pop()`) - Dropped the now-unused `NativeStackNavigationProp` import 4. **Removed a redundant cast** (`app/components/UI/Predict/views/PredictMarketDetails/PredictMarketDetails.tsx`) - The Polymarket-resolution Webview navigation previously did `(navigation as unknown as AppNavigationProp).navigate(...)` because `navigation` was typed to the Predict stack. Now that `navigation` is `AppNavigationProp`, the cast (and its TODO) are gone 5. **`PredictEntryPoint` widened** (`app/components/UI/Predict/types/navigation.ts`) - Added `REWARDS`, `GTM_MODAL`, `BUY_PREVIEW` entry points that were already used at runtime but not part of the type union 6. **`AppStackNavigationProp` JSDoc clarified** (`app/core/NavigationService/types.ts`) - Documents that it is for stack-only APIs (incl. native-stack events like `transitionEnd`) and remains keyed to the loose global `RootParamList` during Phase 4; prefer `AppNavigationProp` for strict route/param checking ## **Changelog** <!-- mms-check: type=changelog required=true blocking=true --> <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry:null ## **Related issues** <!-- mms-check: type=issue-link required=true --> Fixes:https://consensyssoftware.atlassian.net/browse/MCWP-675 ## **Manual testing steps** <!-- mms-check: type=manual-testing required=true --> N/A ## **Screenshots/Recordings** <!-- mms-check: type=screenshot required=true --> <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> N/A ## **Pre-merge author checklist** <!-- mms-check: type=checklist required=true --> <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Types-only refactor across Predict navigation; no logic changes beyond removing an unsafe cast that should now type-check correctly. > > **Overview** > **Phase 4** of the React Navigation typing migration: Predict production code now uses app-wide navigation types instead of untyped `useNavigation()` or `NavigationProp<PredictNavigationParamList>`. > > Across **~50+ Predict files** (components, views, hooks, contexts), call sites switch to `useNavigation<AppNavigationProp>()` with imports from `core/NavigationService/types`. **`usePredictActionGuard`** now expects `AppNavigationProp`, so guarded flows align with root stack typing. **`usePredictBuyActions`** uses `AppStackNavigationProp` for stack-only APIs (e.g. `transitionEnd`, `StackActions`). > > In **`PredictMarketDetails`**, Polymarket resolution Webview navigation drops the `(navigation as unknown as AppNavigationProp)` cast now that navigation is already root-typed. > > **`PredictEntryPoint`** adds `REWARDS`, `GTM_MODAL`, and `BUY_PREVIEW` to match runtime analytics usage. **`AppStackNavigationProp`** JSDoc notes when to prefer it vs `AppNavigationProp` during Phase 4. > > No intended runtime behavior change—compile-time route/param checking against `RootStackParamList`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1c81771. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent cd436a6 commit 74dbb8d

54 files changed

Lines changed: 155 additions & 202 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/components/UI/Predict/components/FeaturedCarousel/FeaturedCarouselCard.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React, { useCallback } from 'react';
22
import { TouchableOpacity } from 'react-native';
33
import { Image } from 'expo-image';
4-
import { NavigationProp, useNavigation } from '@react-navigation/native';
4+
import { useNavigation } from '@react-navigation/native';
5+
import type { AppNavigationProp } from '../../../../../core/NavigationService/types';
56
import { useTailwind } from '@metamask/design-system-twrnc-preset';
67
import {
78
Box,
@@ -21,10 +22,7 @@ import {
2122
PredictOutcome,
2223
PredictOutcomeToken,
2324
} from '../../types';
24-
import {
25-
PredictNavigationParamList,
26-
PredictEntryPoint,
27-
} from '../../types/navigation';
25+
import { PredictEntryPoint } from '../../types/navigation';
2826
import { formatPercentage } from '../../utils/format';
2927
import { PredictEventValues } from '../../constants/eventNames';
3028
import { usePredictActionGuard } from '../../hooks/usePredictActionGuard';
@@ -47,8 +45,7 @@ const FeaturedCarouselCard: React.FC<FeaturedCarouselCardProps> = ({
4745
entryPoint = PredictEventValues.ENTRY_POINT.PREDICT_FEED,
4846
}) => {
4947
const tw = useTailwind();
50-
const navigation =
51-
useNavigation<NavigationProp<PredictNavigationParamList>>();
48+
const navigation = useNavigation<AppNavigationProp>();
5249
const { openBuySheet } = usePredictPreviewSheet();
5350
const { executeGuardedAction } = usePredictActionGuard({ navigation });
5451

app/components/UI/Predict/components/FeaturedCarousel/FeaturedCarouselSportCard.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useCallback, useMemo } from 'react';
22
import { TouchableOpacity } from 'react-native';
3-
import { NavigationProp, useNavigation } from '@react-navigation/native';
3+
import { useNavigation } from '@react-navigation/native';
4+
import type { AppNavigationProp } from '../../../../../core/NavigationService/types';
45
import { useSelector } from 'react-redux';
56
import { useTailwind } from '@metamask/design-system-twrnc-preset';
67
import {
@@ -25,10 +26,7 @@ import {
2526
PredictOutcomeToken,
2627
PredictSportTeam,
2728
} from '../../types';
28-
import {
29-
PredictNavigationParamList,
30-
PredictEntryPoint,
31-
} from '../../types/navigation';
29+
import { PredictEntryPoint } from '../../types/navigation';
3230
import { formatPercentage } from '../../utils/format';
3331
import { PredictEventValues } from '../../constants/eventNames';
3432
import { usePredictActionGuard } from '../../hooks/usePredictActionGuard';
@@ -66,8 +64,7 @@ const FeaturedCarouselSportCard: React.FC<FeaturedCarouselSportCardProps> = ({
6664
entryPoint = PredictEventValues.ENTRY_POINT.PREDICT_FEED,
6765
}) => {
6866
const tw = useTailwind();
69-
const navigation =
70-
useNavigation<NavigationProp<PredictNavigationParamList>>();
67+
const navigation = useNavigation<AppNavigationProp>();
7168
const { openBuySheet } = usePredictPreviewSheet();
7269
const { executeGuardedAction } = usePredictActionGuard({ navigation });
7370
const livePricesEnabled = useSelector(

app/components/UI/Predict/components/PredictActivity/PredictActivity.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { formatPercentage, formatPrice } from '../../utils/format';
1313
import { strings } from '../../../../../../locales/i18n';
1414
import Routes from '../../../../../constants/navigation/Routes';
1515
import { useNavigation } from '@react-navigation/native';
16+
import type { AppNavigationProp } from '../../../../../core/NavigationService/types';
1617
import { PredictActivityItem, PredictActivityType } from '../../types';
1718
import { useAnalytics } from '../../../../hooks/useAnalytics/useAnalytics';
1819
import { MonetizedPrimitive } from '../../../../../core/Analytics/MetaMetrics.types';
@@ -38,7 +39,7 @@ const PredictActivity: React.FC<PredictActivityProps> = ({
3839
containerStyle,
3940
}) => {
4041
const tw = useTailwind();
41-
const navigation = useNavigation();
42+
const navigation = useNavigation<AppNavigationProp>();
4243
const { trackEvent, createEventBuilder } = useAnalytics();
4344
const isDebit = item.type === PredictActivityType.BUY;
4445
const signedAmount = `${isDebit ? '-' : '+'}${formatPrice(

app/components/UI/Predict/components/PredictActivityDetail/PredictActivityDetail.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
2+
import type { AppNavigationProp } from '../../../../../core/NavigationService/types';
23
import React, { useMemo, useEffect } from 'react';
34
import { SafeAreaView } from 'react-native-safe-area-context';
45
import { useSelector } from 'react-redux';
@@ -35,7 +36,7 @@ import { PredictActivityDetailsSelectorsIDs } from '../../Predict.testIds';
3536
interface PredictActivityDetailProps {}
3637

3738
const PredictActivityDetails: React.FC<PredictActivityDetailProps> = () => {
38-
const navigation = useNavigation();
39+
const navigation = useNavigation<AppNavigationProp>();
3940
const route =
4041
useRoute<RouteProp<PredictNavigationParamList, 'PredictActivityDetail'>>();
4142
const { activity } = route.params || {};

app/components/UI/Predict/components/PredictAddFundsSheet/PredictAddFundsSheet.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import BottomSheetHeader from '../../../../../component-library/components/Botto
1616
import { ButtonVariants } from '../../../../../component-library/components/Buttons/Button/Button.types';
1717
import { usePredictDeposit } from '../../hooks/usePredictDeposit';
1818
import { usePredictActionGuard } from '../../hooks/usePredictActionGuard';
19-
import { NavigationProp, useNavigation } from '@react-navigation/native';
20-
import { PredictNavigationParamList } from '../../types/navigation';
19+
import { useNavigation } from '@react-navigation/native';
20+
import type { AppNavigationProp } from '../../../../../core/NavigationService/types';
2121
import { PredictEventValues } from '../../constants/eventNames';
2222
import {
2323
usePredictBottomSheet,
@@ -35,8 +35,7 @@ const PredictAddFundsSheet = forwardRef<
3535
PredictAddFundsSheetProps
3636
>(({ onDismiss }, ref) => {
3737
const tw = useTailwind();
38-
const navigation =
39-
useNavigation<NavigationProp<PredictNavigationParamList>>();
38+
const navigation = useNavigation<AppNavigationProp>();
4039
const { deposit } = usePredictDeposit();
4140
const { executeGuardedAction } = usePredictActionGuard({
4241
navigation,

app/components/UI/Predict/components/PredictBalance/PredictBalance.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
import { useTailwind } from '@metamask/design-system-twrnc-preset';
1111
import images from 'images/image-icons';
1212
import React, { useCallback, useEffect, useMemo } from 'react';
13-
import { NavigationProp, useNavigation } from '@react-navigation/native';
13+
import { useNavigation } from '@react-navigation/native';
14+
import type { AppNavigationProp } from '../../../../../core/NavigationService/types';
1415
import { useQueryClient } from '@tanstack/react-query';
1516
import { useSelector } from 'react-redux';
1617
import { predictQueries } from '../../queries';
@@ -43,7 +44,6 @@ import { usePredictBalance } from '../../hooks/usePredictBalance';
4344
import { usePredictDeposit } from '../../hooks/usePredictDeposit';
4445
import { formatPrice } from '../../utils/format';
4546
import { usePredictActionGuard } from '../../hooks/usePredictActionGuard';
46-
import { PredictNavigationParamList } from '../../types/navigation';
4747
import { usePredictWithdraw } from '../../hooks/usePredictWithdraw';
4848
import { usePredictAccountState } from '../../hooks/usePredictAccountState';
4949
import { PredictEventValues } from '../../constants/eventNames';
@@ -71,8 +71,7 @@ const PredictBalance: React.FC<PredictBalanceProps> = ({
7171
selectPredictPortfolioEnabledFlag,
7272
);
7373

74-
const navigation =
75-
useNavigation<NavigationProp<PredictNavigationParamList>>();
74+
const navigation = useNavigation<AppNavigationProp>();
7675

7776
const queryClient = useQueryClient();
7877
const {

app/components/UI/Predict/components/PredictCryptoUpDownMarketCard/PredictCryptoUpDownMarketCard.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import {
3535
TextVariant,
3636
} from '@metamask/design-system-react-native';
3737
import { useTailwind } from '@metamask/design-system-twrnc-preset';
38-
import { NavigationProp, useNavigation } from '@react-navigation/native';
38+
import { useNavigation } from '@react-navigation/native';
39+
import type { AppNavigationProp } from '../../../../../core/NavigationService/types';
3940
import { useTheme } from '../../../../../util/theme';
4041
import { PredictEventValues } from '../../constants/eventNames';
4142
import { usePredictActionGuard } from '../../hooks/usePredictActionGuard';
@@ -54,10 +55,7 @@ import {
5455
type PredictMarketBuyButtonPress,
5556
type PriceQuery,
5657
} from '../../types';
57-
import type {
58-
PredictEntryPoint,
59-
PredictNavigationParamList,
60-
} from '../../types/navigation';
58+
import type { PredictEntryPoint } from '../../types/navigation';
6159
import {
6260
getEventStartTime,
6361
getCryptoSymbol,
@@ -1124,8 +1122,7 @@ const PredictCryptoUpDownMarketCard: React.FC<
11241122
const isCompact = isCarousel;
11251123
const tw = useTailwind();
11261124
const { colors } = useTheme();
1127-
const navigation =
1128-
useNavigation<NavigationProp<PredictNavigationParamList>>();
1125+
const navigation = useNavigation<AppNavigationProp>();
11291126
const { navigateToMarketDetails } = usePredictNavigation();
11301127
const { openBuySheet } = usePredictPreviewSheet();
11311128
const resolvedEntryPoint = useResolvedPredictEntryPoint(propEntryPoint);

app/components/UI/Predict/components/PredictCryptoUpDownPositions/PredictCryptoUpDownPosition.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@ import {
1010
TextVariant,
1111
} from '@metamask/design-system-react-native';
1212
import { useTailwind } from '@metamask/design-system-twrnc-preset';
13-
import {
14-
NavigationProp,
15-
useIsFocused,
16-
useNavigation,
17-
} from '@react-navigation/native';
13+
import { useIsFocused, useNavigation } from '@react-navigation/native';
14+
import type { AppNavigationProp } from '../../../../../core/NavigationService/types';
1815
import { useSelector } from 'react-redux';
1916
import { strings } from '../../../../../../locales/i18n';
2017
import Button, {
@@ -45,7 +42,6 @@ import {
4542
type PredictMarket,
4643
type PredictPosition,
4744
} from '../../types';
48-
import { PredictNavigationParamList } from '../../types/navigation';
4945
import { formatCents, formatPrice } from '../../utils/format';
5046

5147
const AUTO_REFRESH_TIMEOUT = 5000;
@@ -62,8 +58,7 @@ const PredictCryptoUpDownPosition: React.FC<
6258
const tw = useTailwind();
6359
const privacyMode = useSelector(selectPrivacyMode);
6460
const isFocused = useIsFocused();
65-
const navigation =
66-
useNavigation<NavigationProp<PredictNavigationParamList>>();
61+
const navigation = useNavigation<AppNavigationProp>();
6762

6863
const { onCashOut } = usePredictCashOut({
6964
market,

app/components/UI/Predict/components/PredictDetailsHeaderSkeleton/PredictDetailsHeaderSkeleton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { useTailwind } from '@metamask/design-system-twrnc-preset';
99
import { Pressable } from 'react-native';
1010
import { useNavigation } from '@react-navigation/native';
11+
import type { AppNavigationProp } from '../../../../../core/NavigationService/types';
1112
import { useSafeAreaInsets } from 'react-native-safe-area-context';
1213
import Icon, {
1314
IconName,
@@ -27,7 +28,7 @@ const PredictDetailsHeaderSkeleton: React.FC<
2728
PredictDetailsHeaderSkeletonProps
2829
> = ({ testID = PREDICT_DETAILS_HEADER_SKELETON }) => {
2930
const tw = useTailwind();
30-
const navigation = useNavigation();
31+
const navigation = useNavigation<AppNavigationProp>();
3132
const insets = useSafeAreaInsets();
3233

3334
return (

app/components/UI/Predict/components/PredictGTMModal/PredictGTMModal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useNavigation } from '@react-navigation/native';
2+
import type { AppNavigationProp } from '../../../../../core/NavigationService/types';
23
import React, { useState, useEffect } from 'react';
34
import { Image, View } from 'react-native';
45
import Animated, {
@@ -37,7 +38,7 @@ import { PREDICT_GTM_MODAL_TEST_IDS } from './PredictGTMModal.testIds';
3738

3839
const PredictGTMModal = () => {
3940
const { trackEvent, createEventBuilder } = useAnalytics();
40-
const { navigate } = useNavigation();
41+
const { navigate } = useNavigation<AppNavigationProp>();
4142
const theme = useTheme();
4243
const [imageLoaded, setImageLoaded] = useState(false);
4344
const opacity = useSharedValue(0);

0 commit comments

Comments
 (0)