From 90b6a1d06ca7f1692be84c762007bd6354899462 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Fri, 7 Mar 2025 15:04:57 -0600 Subject: [PATCH] Prevent rerenders of the AccountListItem --- .../account-list-item/account-list-item.js | 12 +- .../account-list-menu/account-list-menu.tsx | 128 ++++++++++-------- ui/pages/routes/routes.component.js | 2 +- ui/selectors/accounts.ts | 9 +- ui/selectors/selectors.js | 6 +- 5 files changed, 89 insertions(+), 68 deletions(-) diff --git a/ui/components/multichain/account-list-item/account-list-item.js b/ui/components/multichain/account-list-item/account-list-item.js index 3fb8345190f2..044c29762114 100644 --- a/ui/components/multichain/account-list-item/account-list-item.js +++ b/ui/components/multichain/account-list-item/account-list-item.js @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta) @@ -163,10 +163,12 @@ const AccountListItem = ({ formattedTokensWithBalancesPerChain, ); // cross chain agg balance - const mappedOrderedTokenList = accountTotalFiatBalances.orderedTokenList.map( - (item) => ({ - avatarValue: item.iconUrl, - }), + const mappedOrderedTokenList = useMemo( + () => + accountTotalFiatBalances.orderedTokenList.map((item) => ({ + avatarValue: item.iconUrl, + })), + [accountTotalFiatBalances.orderedTokenList], ); let balanceToTranslate; if (isEvmNetwork) { diff --git a/ui/components/multichain/account-list-menu/account-list-menu.tsx b/ui/components/multichain/account-list-menu/account-list-menu.tsx index 0c1b996b66e1..2a5faef1fa22 100644 --- a/ui/components/multichain/account-list-menu/account-list-menu.tsx +++ b/ui/components/multichain/account-list-menu/account-list-menu.tsx @@ -312,19 +312,23 @@ export const AccountListMenu = ({ ); ///: END:ONLY_INCLUDE_IF - let searchResults: MergedInternalAccount[] = filteredUpdatedAccountList; - if (searchQuery) { - const fuse = new Fuse(filteredAccounts, { - threshold: 0.2, - location: 0, - distance: 100, - maxPatternLength: 32, - minMatchCharLength: 1, - keys: ['metadata.name', 'address'], - }); - fuse.setCollection(filteredAccounts); - searchResults = fuse.search(searchQuery); - } + const searchResults: MergedInternalAccount[] = useMemo(() => { + let _searchResults: MergedInternalAccount[] = filteredUpdatedAccountList; + if (searchQuery) { + const fuse = new Fuse(filteredAccounts, { + threshold: 0.2, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + keys: ['metadata.name', 'address'], + }); + fuse.setCollection(filteredAccounts); + _searchResults = fuse.search(searchQuery); + } + + return _searchResults; + }, [filteredAccounts, filteredUpdatedAccountList, searchQuery]); const title = useMemo( () => getActionTitle(t as (text: string) => string, actionMode), @@ -342,7 +346,7 @@ export const AccountListMenu = ({ } const onAccountListItemItemClicked = useCallback( - (account) => { + (account: MergedInternalAccount) => { return () => { onClose(); trackEvent({ @@ -365,9 +369,59 @@ export const AccountListMenu = ({ dispatch(setSelectedAccount(account.address)); }; }, - [dispatch, onClose, trackEvent], + [dispatch, onClose, trackEvent, defaultHomeActiveTabName], ); + const accountListItems = useMemo(() => { + return searchResults.map((account) => { + const connectedSite = connectedSites[account.address]?.find( + ({ origin }) => origin === currentTabOrigin, + ); + + const hideAccountListItem = searchQuery.length === 0 && account.hidden; + + /* NOTE: Hidden account will be displayed only in the search list */ + + return ( + + + + ); + }); + }, [ + searchResults, + connectedSites, + currentTabOrigin, + privacyMode, + accountListItemProps, + selectedAccount, + onClose, + onAccountListItemItemClicked, + searchQuery, + ]); + return ( @@ -713,9 +767,8 @@ export const AccountListMenu = ({ size: Size.SM, }} inputProps={{ autoFocus: true }} - // TODO: These props are required in the TextFieldSearch component. These should be optional - endAccessory - className + endAccessory={null} + className="" /> ) : null} @@ -731,44 +784,7 @@ export const AccountListMenu = ({ {t('noAccountsFound')} ) : null} - {searchResults.map((account) => { - const connectedSite = connectedSites[account.address]?.find( - ({ origin }) => origin === currentTabOrigin, - ); - - const hideAccountListItem = - searchQuery.length === 0 && account.hidden; - - /* NOTE: Hidden account will be displayed only in the search list */ - - return ( - - - - ); - })} + {accountListItems} {/* Hidden Accounts, this component shows hidden accounts in account list Item*/} {hiddenAddresses.length > 0 ? ( diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index fe0f5ba3f1b5..83b207d4c61b 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -483,7 +483,7 @@ export default class Routes extends Component { {showOnboardingHeader(location) && } {isAccountMenuOpen ? ( toggleAccountMenu()} + onClose={toggleAccountMenu} privacyMode={privacyMode} /> ) : null} diff --git a/ui/selectors/accounts.ts b/ui/selectors/accounts.ts index 5d4a99f456d6..b77f6e5431be 100644 --- a/ui/selectors/accounts.ts +++ b/ui/selectors/accounts.ts @@ -5,6 +5,7 @@ import { } from '@metamask/keyring-api'; import { InternalAccount } from '@metamask/keyring-internal-api'; import { AccountsControllerState } from '@metamask/accounts-controller'; +import { createSelector } from 'reselect'; import { isBtcMainnetAddress, isBtcTestnetAddress, @@ -26,9 +27,11 @@ export function isSolanaAccount(account: InternalAccount) { return Boolean(account && account.type === DataAccount); } -export function getInternalAccounts(state: AccountsState) { - return Object.values(state.metamask.internalAccounts.accounts); -} +export const getInternalAccounts = createSelector( + (state: AccountsState) => + Object.values(state.metamask.internalAccounts.accounts), + (accounts) => accounts, +); export function getSelectedInternalAccount(state: AccountsState) { const accountId = state.metamask.internalAccounts.selectedAccount; diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index a46fdf5fbff1..bd1b9c88e16d 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -376,7 +376,7 @@ export function getAccountTypeForKeyring(keyring) { /** * Get MetaMask accounts, including account name and balance. */ -export const getMetaMaskAccounts = createSelector( +export const getMetaMaskAccounts = createDeepEqualSelector( getInternalAccounts, getMetaMaskAccountBalances, getMetaMaskCachedBalances, @@ -501,7 +501,7 @@ export const getSelectedEvmInternalAccount = createSelector( * @param accounts - The object containing the accounts. * @returns The array of internal accounts sorted by keyring. */ -export const getInternalAccountsSortedByKeyring = createSelector( +export const getInternalAccountsSortedByKeyring = createDeepEqualSelector( getMetaMaskKeyrings, getMetaMaskAccounts, (keyrings, accounts) => { @@ -797,7 +797,7 @@ function getNativeTokenInfo(state, chainId) { * * @returns {InternalAccountWithBalance} An array of internal accounts with balance */ -export const getMetaMaskAccountsOrdered = createSelector( +export const getMetaMaskAccountsOrdered = createDeepEqualSelector( getInternalAccountsSortedByKeyring, getMetaMaskAccounts, (internalAccounts, accounts) => {