Skip to content

Commit 3595344

Browse files
perf: improve predict explore re-renders (#30135)
## **Description** Reduce tab performance degredation in explore - optimise predict rerenders. From 60+ renders to 2/3 ## **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: perf: improve predict explore re-renders ## **Related issues** Fixes: ## **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** https://www.loom.com/share/96f8b59522674684a93076e6e8e7c4fc ### **After** https://www.loom.com/share/34bffb5bcb59430286dac312e660d11e ## **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] > **Low Risk** > Low risk refactor focused on render performance: it only restructures how bet button press handlers are memoized and passed down, without changing pricing/market logic or external APIs. > > **Overview** > Reduces re-renders in `PredictActionButtons` by extracting the OPEN-market bet button rendering into a new `PredictBetButtonsContainer` and memoizing `onYesPress`/`onNoPress` with `useCallback` (and the optional `onDrawPress`). > > This keeps the bet button UI/props the same while avoiding new inline handler functions on each render. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5dba77f. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 4669b3a commit 3595344

1 file changed

Lines changed: 50 additions & 20 deletions

File tree

app/components/UI/Predict/components/PredictActionButtons/PredictActionButtons.tsx

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo } from 'react';
1+
import React, { useCallback, useMemo } from 'react';
22
import { Box } from '@metamask/design-system-react-native';
33
import PredictBetButtons from './PredictBetButtons';
44
import PredictClaimButton from './PredictClaimButton';
@@ -166,30 +166,60 @@ const PredictActionButtons: React.FC<PredictActionButtonsProps> = ({
166166
}
167167

168168
if (market.status === PredictMarketStatus.OPEN && buttonConfig) {
169-
const drawToken = buttonConfig.drawToken;
170-
171169
return (
172-
<Box twClassName="w-full mt-4">
173-
<PredictBetButtons
174-
yesLabel={buttonConfig.yesLabel}
175-
yesPrice={buttonConfig.yesPrice}
176-
onYesPress={() => onBetPress(buttonConfig.yesToken)}
177-
drawLabel={buttonConfig.drawLabel}
178-
drawPrice={buttonConfig.drawPrice}
179-
onDrawPress={drawToken ? () => onBetPress(drawToken) : undefined}
180-
noLabel={buttonConfig.noLabel}
181-
noPrice={buttonConfig.noPrice}
182-
onNoPress={() => onBetPress(buttonConfig.noToken)}
183-
yesTeamColor={buttonConfig.yesTeamColor}
184-
noTeamColor={buttonConfig.noTeamColor}
185-
testID={`${testID}${PREDICT_ACTION_BUTTONS_TEST_IDS.PREDICT_BET_BUTTON}`}
186-
isCarousel={isCarousel}
187-
/>
188-
</Box>
170+
<PredictBetButtonsContainer
171+
buttonConfig={buttonConfig}
172+
onBetPress={onBetPress}
173+
testID={testID}
174+
isCarousel={isCarousel}
175+
/>
189176
);
190177
}
191178

192179
return null;
193180
};
194181

182+
function PredictBetButtonsContainer(props: {
183+
buttonConfig: ButtonConfig;
184+
onBetPress: (token: PredictOutcomeToken) => void;
185+
testID: string;
186+
isCarousel?: boolean;
187+
}) {
188+
const { buttonConfig, onBetPress, testID, isCarousel } = props;
189+
const { yesToken, drawToken, noToken } = buttonConfig;
190+
191+
const onYesPress = useCallback(
192+
() => onBetPress(yesToken),
193+
[onBetPress, yesToken],
194+
);
195+
const onDrawPress = useMemo(
196+
() => (drawToken ? () => onBetPress(drawToken) : undefined),
197+
[onBetPress, drawToken],
198+
);
199+
const onNoPress = useCallback(
200+
() => onBetPress(noToken),
201+
[onBetPress, noToken],
202+
);
203+
204+
return (
205+
<Box twClassName="w-full mt-4">
206+
<PredictBetButtons
207+
yesLabel={buttonConfig.yesLabel}
208+
yesPrice={buttonConfig.yesPrice}
209+
onYesPress={onYesPress}
210+
drawLabel={buttonConfig.drawLabel}
211+
drawPrice={buttonConfig.drawPrice}
212+
onDrawPress={onDrawPress}
213+
noLabel={buttonConfig.noLabel}
214+
noPrice={buttonConfig.noPrice}
215+
onNoPress={onNoPress}
216+
yesTeamColor={buttonConfig.yesTeamColor}
217+
noTeamColor={buttonConfig.noTeamColor}
218+
testID={`${testID}${PREDICT_ACTION_BUTTONS_TEST_IDS.PREDICT_BET_BUTTON}`}
219+
isCarousel={isCarousel}
220+
/>
221+
</Box>
222+
);
223+
}
224+
195225
export default PredictActionButtons;

0 commit comments

Comments
 (0)