Skip to content

Commit 2ccad65

Browse files
authored
feat: allow custom sorting in perps from explore cp-7.77.0 (#29912)
<!-- 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. --> ## **Description** Allow custom sorting in perps from explore <!-- 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? --> ## **Changelog** <!-- 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: allow custom sorting in perps from explore ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/ASSETS-3170 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** https://github.com/user-attachments/assets/b149183e-451c-4345-8e44-e9c8b01dbb61 <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- 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. --> - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] 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] > **Medium Risk** > Medium risk because it changes default sorting behavior and navigation params for the Perps market list, which can affect perceived ordering and persisted sort direction across screens. > > **Overview** > **Explore → Perps market list navigation now forwards a default sort option** via new `defaultSortOptionId` route param so the full market list opens with the same ordering as the Explore feed. > > `usePerpsMarketListView` accepts this override and **resets sort direction to the controller default when the override differs from the user’s saved option**, while preserving the saved direction when it matches. `usePerpsFeed` now exposes a per-variant `defaultSortOptionId` (backed by `PERPS_VARIANT_SORT_OPTION`) and keeps search ordering consistent (Fuse relevance preserved for non-macro; macro still sorts by volume). > > Explore perps sections (`NowTab`, `CryptoTab`, `MacroTab`, `RwasTab`, `PerpsToggleBlock`) were updated to pass the sort option through, and new/updated unit tests cover the override logic and navigation wiring. `useStocksFeed` was adjusted so **search results include RWAs across chains**, while the tab view remains Ethereum-only, with tests added. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ddc377c. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent b1361a1 commit 2ccad65

17 files changed

Lines changed: 865 additions & 37 deletions

app/components/UI/Perps/Views/PerpsMarketListView/PerpsMarketListView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const PerpsMarketListView = ({
6767
route.params?.showWatchlistOnly ?? propShowWatchlistOnly ?? false;
6868
const defaultMarketTypeFilter =
6969
route.params?.defaultMarketTypeFilter ?? 'all';
70+
const defaultSortOptionId = route.params?.defaultSortOptionId;
7071

7172
const fadeAnimation = useRef(new Animated.Value(0)).current;
7273
const [isSortFieldSheetVisible, setIsSortFieldSheetVisible] = useState(false);
@@ -84,6 +85,7 @@ const PerpsMarketListView = ({
8485
enablePolling: false,
8586
showWatchlistOnly,
8687
defaultMarketTypeFilter,
88+
defaultSortOptionId,
8789
showZeroVolume: __DEV__,
8890
});
8991

app/components/UI/Perps/hooks/usePerpsMarketListView.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,68 @@ describe('usePerpsMarketListView', () => {
305305
});
306306
});
307307

308+
it('defaultSortOptionId overrides the saved sort option and resets direction to default', () => {
309+
let selectorCallCount = 0;
310+
mockUseSelector.mockImplementation(() => {
311+
selectorCallCount++;
312+
if (selectorCallCount % 2 === 1) {
313+
return ['BTC'];
314+
}
315+
// Saved preference is volume/asc — user had it sorted ascending
316+
return { optionId: 'volume', direction: 'asc' };
317+
});
318+
319+
renderHook(() =>
320+
usePerpsMarketListView({ defaultSortOptionId: 'priceChange' }),
321+
);
322+
323+
// Option overridden → direction must reset to default (desc), not carry 'asc'
324+
expect(mockUsePerpsSorting).toHaveBeenCalledWith({
325+
initialOptionId: 'priceChange',
326+
initialDirection: 'desc',
327+
});
328+
});
329+
330+
it('preserves saved direction when defaultSortOptionId matches the saved option', () => {
331+
let selectorCallCount = 0;
332+
mockUseSelector.mockImplementation(() => {
333+
selectorCallCount++;
334+
if (selectorCallCount % 2 === 1) {
335+
return ['BTC'];
336+
}
337+
// Saved preference is priceChange/asc
338+
return { optionId: 'priceChange', direction: 'asc' };
339+
});
340+
341+
renderHook(() =>
342+
usePerpsMarketListView({ defaultSortOptionId: 'priceChange' }),
343+
);
344+
345+
// Same option — carry the saved direction, don't reset
346+
expect(mockUsePerpsSorting).toHaveBeenCalledWith({
347+
initialOptionId: 'priceChange',
348+
initialDirection: 'asc',
349+
});
350+
});
351+
352+
it('falls back to saved sort preference when defaultSortOptionId is not provided', () => {
353+
let selectorCallCount = 0;
354+
mockUseSelector.mockImplementation(() => {
355+
selectorCallCount++;
356+
if (selectorCallCount % 2 === 1) {
357+
return ['BTC'];
358+
}
359+
return { optionId: 'fundingRate', direction: 'asc' };
360+
});
361+
362+
renderHook(() => usePerpsMarketListView());
363+
364+
expect(mockUsePerpsSorting).toHaveBeenCalledWith({
365+
initialOptionId: 'fundingRate',
366+
initialDirection: 'asc',
367+
});
368+
});
369+
308370
it('exposes sort state correctly', () => {
309371
const { result } = renderHook(() => usePerpsMarketListView());
310372

app/components/UI/Perps/hooks/usePerpsMarketListView.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { usePerpsMarkets } from './usePerpsMarkets';
44
import { usePerpsSearch } from './usePerpsSearch';
55
import { usePerpsSorting } from './usePerpsSorting';
66
import {
7+
MARKET_SORTING_CONFIG,
78
sortMarkets,
89
type PerpsMarketData,
910
type MarketTypeFilter,
@@ -33,6 +34,11 @@ interface UsePerpsMarketListViewParams {
3334
* @default 'all'
3435
*/
3536
defaultMarketTypeFilter?: MarketTypeFilter;
37+
/**
38+
* Initial sort option ID — overrides the persisted user preference when provided.
39+
* @default undefined (falls back to saved user preference)
40+
*/
41+
defaultSortOptionId?: SortOptionId;
3642
/**
3743
* Show markets with $0.00 volume
3844
* @default false
@@ -133,6 +139,7 @@ export const usePerpsMarketListView = ({
133139
enablePolling = false,
134140
showWatchlistOnly = false,
135141
defaultMarketTypeFilter = 'all',
142+
defaultSortOptionId,
136143
showZeroVolume = false,
137144
}: UsePerpsMarketListViewParams = {}): UsePerpsMarketListViewReturn => {
138145
// Fetch markets data
@@ -196,10 +203,20 @@ export const usePerpsMarketListView = ({
196203
return searchedMarkets;
197204
}, [searchedMarkets, marketTypeFilter]);
198205

199-
// Use sorting hook for sort state and sorting logic
206+
// Use sorting hook for sort state and sorting logic.
207+
// defaultSortOptionId (from navigation params) takes precedence over the saved user
208+
// preference. When it overrides a *different* option, reset direction to the default
209+
// so the market list opens sorted the same way the explore feed displayed it (always desc).
210+
// When there is no override, or the override matches the saved option, carry the saved direction.
211+
const isOptionOverridden =
212+
defaultSortOptionId !== undefined &&
213+
defaultSortOptionId !== savedSortPreference.optionId;
200214
const sortingHook = usePerpsSorting({
201-
initialOptionId: savedSortPreference.optionId as SortOptionId,
202-
initialDirection: savedSortPreference.direction,
215+
initialOptionId: (defaultSortOptionId ??
216+
savedSortPreference.optionId) as SortOptionId,
217+
initialDirection: isOptionOverridden
218+
? MARKET_SORTING_CONFIG.DefaultDirection
219+
: savedSortPreference.direction,
203220
});
204221

205222
// Wrap handleOptionChange to save preference to PerpsController

app/components/UI/Perps/types/navigation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
type OrderType,
66
type PerpsMarketData,
77
type TPSLTrackingData,
8+
type SortOptionId,
89
} from '@metamask/perps-controller';
910
import { PerpsTransaction } from './transactionHistory';
1011
import type { DataMonitorParams } from '../hooks/usePerpsDataMonitor';
@@ -90,6 +91,7 @@ export interface PerpsNavigationParamList extends ParamListBase {
9091
| 'commodities'
9192
| 'forex'
9293
| 'new';
94+
defaultSortOptionId?: SortOptionId;
9395
fromHome?: boolean;
9496
button_clicked?: string;
9597
button_location?: string;

0 commit comments

Comments
 (0)