Skip to content

Commit bc0ab56

Browse files
authored
fix: stabilize useWithdrawalRequests selectors to prevent unnecessary re-renders (#29597)
## **Description** Inline `[]` fallbacks in `useWithdrawalRequests` selectors were creating new array references on every render, triggering React-Redux's "Selector unknown returned a different result" warning and causing unnecessary re-renders in `PerpsProgressBar` on mount. Fixed by replacing inline `[]` literals with module-level constants and short-circuiting `.filter()` on empty arrays so selectors always return the same reference. **Performance impact:** FPS on Perps tab mount improved from 60→22 drops down to a stable 60→57, nearly eliminating the frame drop entirely and it doesn't take ~1 second for the Perps screen to show anymore **NOTE**: Unrelated to these changes, there is a considerate amount of frame drop when you're on the Perps home screen and from the UI hydrating. The frames drop as low as 8 FPS. This happens in `main` and this branch as well. Might be worth a follow up ticket ## **Changelog** CHANGELOG entry:null ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: Perps home screen mount Scenario: user opens the app with Perps hub tab visible Given the hub tabs A/B test is enabled And the user has no pending withdrawal requests When the app loads and PerpsProgressBar mounts Then no "Selector unknown returned a different result" warning appears in the console And the Perps tab renders without unnecessary re-renders And FPS remains at or above 57 during mount ``` ## **Screenshots/Recordings** https://github.com/user-attachments/assets/76f6b555-a949-4633-8d4c-aaffffb23f65 ### **Before** https://github.com/user-attachments/assets/4fb102b9-2dac-48c6-9b10-bbe6316825d2 ### **After** https://github.com/user-attachments/assets/4c6688dd-159c-4e9e-89ef-c298de8bc34c ## **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. --> - [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** > Low risk performance-only change: replaces inline empty-array fallbacks with stable module constants to avoid React-Redux selector warnings and unnecessary re-renders; no business logic or API behavior changes expected. > > **Overview** > Prevents unnecessary re-renders in `useWithdrawalRequests` by replacing inline `[]` selector fallbacks with module-level `EMPTY_WITHDRAWAL_REQUESTS`/`EMPTY_TX_HASHES` constants and short-circuiting the withdrawal `.filter()` when there’s no selected address or the list is empty. > > This makes selector outputs stable across renders (eliminating the “selector returned a different result” warning) while keeping the displayed/polled withdrawal logic unchanged. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit e020446. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 8d4e444 commit bc0ab56

1 file changed

Lines changed: 8 additions & 3 deletions

File tree

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ interface UseWithdrawalRequestsResult {
3434
refetch: () => Promise<void>;
3535
}
3636

37+
const EMPTY_WITHDRAWAL_REQUESTS: WithdrawalRequest[] = [];
38+
const EMPTY_TX_HASHES: string[] = [];
39+
3740
const WITHDRAWAL_POLL_INTERVAL_MS = 5000;
3841
const WITHDRAWAL_SEARCH_BUFFER_MS = 60000;
3942

@@ -56,8 +59,10 @@ export const useWithdrawalRequests = (
5659

5760
const allWithdrawals = useStableArray(
5861
usePerpsSelector((state) => {
59-
const withdrawals = state?.withdrawalRequests || [];
60-
if (!selectedAddress) return [];
62+
const withdrawals =
63+
state?.withdrawalRequests ?? EMPTY_WITHDRAWAL_REQUESTS;
64+
if (!selectedAddress || withdrawals.length === 0)
65+
return EMPTY_WITHDRAWAL_REQUESTS;
6166
return withdrawals.filter(
6267
(req) =>
6368
req.accountAddress?.toLowerCase() === selectedAddress.toLowerCase(),
@@ -79,7 +84,7 @@ export const useWithdrawalRequests = (
7984
(state) => state?.lastCompletedWithdrawalTimestamp ?? null,
8085
);
8186
const lastCompletedTxHashes = usePerpsSelector(
82-
(state) => state?.lastCompletedWithdrawalTxHashes ?? [],
87+
(state) => state?.lastCompletedWithdrawalTxHashes ?? EMPTY_TX_HASHES,
8388
);
8489

8590
const initialFetchDoneRef = useRef(false);

0 commit comments

Comments
 (0)