diff --git a/app/ThemeScript.tsx b/app/ThemeScript.tsx
new file mode 100644
index 000000000..2f022f988
--- /dev/null
+++ b/app/ThemeScript.tsx
@@ -0,0 +1,16 @@
+// We inject the theme script to prevent flash of white when the page loads
+const ThemeScript = () => {
+ const themeScript = `
+ (function() {
+ const storedTheme = localStorage.getItem('theme')
+ if (storedTheme === '"light"') return;
+ if (storedTheme === '"dark"' || window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ document.documentElement.classList.add('dark')
+ }
+ })()
+ `;
+
+ return ;
+};
+
+export default ThemeScript;
diff --git a/app/[locale]/exploits/[slug]/ExploitChecker.tsx b/app/[locale]/exploits/[slug]/ExploitChecker.tsx
index 6cabbfb1d..1bcc034cb 100644
--- a/app/[locale]/exploits/[slug]/ExploitChecker.tsx
+++ b/app/[locale]/exploits/[slug]/ExploitChecker.tsx
@@ -4,7 +4,7 @@ import AddressForm from 'components/exploits/AddressForm';
import ExploitChecker from 'components/exploits/ExploitChecker';
import { AddressPageContextProvider } from 'lib/hooks/page-context/AddressPageContext';
import { Exploit, getUniqueChainIds } from 'lib/utils/exploits';
-import { Suspense, useState } from 'react';
+import { Suspense, useMemo, useState } from 'react';
import { Address } from 'viem';
interface Props {
@@ -13,11 +13,12 @@ interface Props {
const ExploitCheckerWrapper = ({ exploit }: Props) => {
const [address, setAddress] = useState
();
+ const chainIds = useMemo(() => getUniqueChainIds(exploit), [exploit]);
return (
-
-
+
+
diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx
index 8c4d4d4ec..1fe864461 100644
--- a/app/[locale]/layout.tsx
+++ b/app/[locale]/layout.tsx
@@ -1,5 +1,6 @@
import { SpeedInsights } from '@vercel/speed-insights/next';
import Analytics from 'app/Analytics';
+import ThemeScript from 'app/ThemeScript';
import ToastifyConfig from 'components/common/ToastifyConfig';
import TopLoader from 'components/common/TopLoader';
import Footer from 'components/footer/Footer';
@@ -62,6 +63,7 @@ const MainLayout = ({ children, params }: Props) => {
return (
+
diff --git a/app/[locale]/learn/wallets/add-network/[slug]/AddNetworkChainSelect.tsx b/app/[locale]/learn/wallets/add-network/[slug]/AddNetworkChainSelect.tsx
index ebc628443..00752dd0d 100644
--- a/app/[locale]/learn/wallets/add-network/[slug]/AddNetworkChainSelect.tsx
+++ b/app/[locale]/learn/wallets/add-network/[slug]/AddNetworkChainSelect.tsx
@@ -2,6 +2,7 @@
import ChainSelectHref from 'components/common/select/ChainSelectHref';
import { getChainSlug } from 'lib/utils/chains';
+import { useCallback } from 'react';
interface Props {
chainId: number;
@@ -9,14 +10,8 @@ interface Props {
// This is a wrapper around ChainSelectHref because we cannot pass the getUrl function as a prop from a server component
const AddNetworkChainSelect = ({ chainId }: Props) => {
- return (
- `/learn/wallets/add-network/${getChainSlug(chainId)}`}
- showNames
- />
- );
+ const getUrl = useCallback((chainId) => `/learn/wallets/add-network/${getChainSlug(chainId)}`, []);
+ return ;
};
export default AddNetworkChainSelect;
diff --git a/app/[locale]/token-approval-checker/[slug]/TokenApprovalCheckerChainSelect.tsx b/app/[locale]/token-approval-checker/[slug]/TokenApprovalCheckerChainSelect.tsx
index 639e5a1d6..6a4d6e15d 100644
--- a/app/[locale]/token-approval-checker/[slug]/TokenApprovalCheckerChainSelect.tsx
+++ b/app/[locale]/token-approval-checker/[slug]/TokenApprovalCheckerChainSelect.tsx
@@ -2,6 +2,7 @@
import ChainSelectHref from 'components/common/select/ChainSelectHref';
import { getChainSlug } from 'lib/utils/chains';
+import { useCallback } from 'react';
interface Props {
chainId: number;
@@ -9,14 +10,8 @@ interface Props {
// This is a wrapper around ChainSelectHref because we cannot pass the getUrl function as a prop from a server component
const TokenApprovalCheckerChainSelect = ({ chainId }: Props) => {
- return (
- `/token-approval-checker/${getChainSlug(chainId)}`}
- showNames
- />
- );
+ const getUrl = useCallback((chainId) => `/token-approval-checker/${getChainSlug(chainId)}`, []);
+ return ;
};
export default TokenApprovalCheckerChainSelect;
diff --git a/components/allowances/controls/BatchRevokeModalWithButton.tsx b/components/allowances/controls/BatchRevokeModalWithButton.tsx
index c34f742cb..936038b2c 100644
--- a/components/allowances/controls/BatchRevokeModalWithButton.tsx
+++ b/components/allowances/controls/BatchRevokeModalWithButton.tsx
@@ -93,7 +93,7 @@ const BatchRevokeModalWithButton = ({ table }: Props) => {
-
+
# |
{t('address.headers.asset')} |
diff --git a/components/common/ChainLogo.tsx b/components/common/ChainLogo.tsx
index 92fcfeb14..c0268effe 100644
--- a/components/common/ChainLogo.tsx
+++ b/components/common/ChainLogo.tsx
@@ -2,6 +2,7 @@
import { useMounted } from 'lib/hooks/useMounted';
import { getChainLogo, getChainName, isSupportedChain } from 'lib/utils/chains';
+import { memo } from 'react';
import { twMerge } from 'tailwind-merge';
import Logo from './Logo';
import PlaceholderIcon from './PlaceholderIcon';
@@ -38,4 +39,4 @@ const ChainLogo = ({ chainId, size, tooltip, className, checkMounted }: Props) =
return ;
};
-export default ChainLogo;
+export default memo(ChainLogo);
diff --git a/components/common/select/ChainSelect.tsx b/components/common/select/ChainSelect.tsx
index 952284dd0..e82703678 100644
--- a/components/common/select/ChainSelect.tsx
+++ b/components/common/select/ChainSelect.tsx
@@ -2,6 +2,7 @@ import ChainLogo from 'components/common/ChainLogo';
import { useColorTheme } from 'lib/hooks/useColorTheme';
import { CHAIN_SELECT_MAINNETS, CHAIN_SELECT_TESTNETS, getChainName, isSupportedChain } from 'lib/utils/chains';
import { useTranslations } from 'next-intl';
+import { memo } from 'react';
import PlaceholderIcon from '../PlaceholderIcon';
import SearchableSelect from './SearchableSelect';
@@ -77,8 +78,10 @@ const ChainSelect = ({ onSelect, selected, menuAlign, chainIds, instanceId, show
minMenuWidth="14.5rem"
placeholder={}
menuAlign={menuAlign}
+ // Note: when searching, option do get unmounted, so there's still some optimization to be done here
+ keepMounted
/>
);
};
-export default ChainSelect;
+export default memo(ChainSelect);
diff --git a/components/common/select/ChainSelectHref.tsx b/components/common/select/ChainSelectHref.tsx
index bd1c7b2fb..df5b9d93d 100644
--- a/components/common/select/ChainSelectHref.tsx
+++ b/components/common/select/ChainSelectHref.tsx
@@ -4,6 +4,7 @@ import ChainLogo from 'components/common/ChainLogo';
import { useRouter } from 'lib/i18n/navigation';
import { CHAIN_SELECT_MAINNETS, CHAIN_SELECT_TESTNETS, getChainName, isSupportedChain } from 'lib/utils/chains';
import { useTranslations } from 'next-intl';
+import { memo } from 'react';
import Button from '../Button';
import PlaceholderIcon from '../PlaceholderIcon';
import SearchableSelect from './SearchableSelect';
@@ -89,4 +90,4 @@ const ChainSelectHref = ({ selected, chainIds, getUrl, instanceId, menuAlign, sh
);
};
-export default ChainSelectHref;
+export default memo(ChainSelectHref);
diff --git a/components/common/select/SearchableSelect.tsx b/components/common/select/SearchableSelect.tsx
index 176190514..66eb49ffe 100644
--- a/components/common/select/SearchableSelect.tsx
+++ b/components/common/select/SearchableSelect.tsx
@@ -1,7 +1,14 @@
'use client';
-import { ReactNode, Ref, forwardRef, useEffect, useRef, useState } from 'react';
-import { ActionMeta, FormatOptionLabelMeta, GroupBase, OnChangeValue, SelectInstance } from 'react-select';
+import { forwardRef, ReactNode, Ref, useEffect, useRef, useState } from 'react';
+import {
+ ActionMeta,
+ createFilter,
+ FormatOptionLabelMeta,
+ GroupBase,
+ OnChangeValue,
+ SelectInstance,
+} from 'react-select';
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
@@ -9,7 +16,7 @@ import Select, { Props as SelectProps } from 'components/common/select/Select';
import Button from '../Button';
import Chevron from '../Chevron';
-import { FilterOptionOption } from 'react-select/dist/declarations/src/filters';
+import type { FilterOptionOption } from 'react-select/dist/declarations/src/filters';
import { twMerge } from 'tailwind-merge';
interface Props> extends SelectProps {
@@ -42,10 +49,9 @@ const SearchableSelect = >(props: P
handleSelectClose();
};
- const handleFiltering = (option: FilterOptionOption, inputValue: string) => {
- const lowerCaseValue = inputValue.toLowerCase();
- return option.value.toLowerCase().includes(lowerCaseValue);
- };
+ const filterOption = createFilter({
+ stringify: (option: FilterOptionOption) => option.value,
+ });
const formatOptionLabel = (option: O, formatOptionLabelMeta: FormatOptionLabelMeta) => {
// 'value' context is handled separately in TargetButton
@@ -68,7 +74,7 @@ const SearchableSelect = >(props: P
onChange={onChange}
className="shrink-0"
menuIsOpen={props.keepMounted ? true : isSelectOpen}
- filterOption={handleFiltering}
+ filterOption={filterOption}
minControlWidth={props.minMenuWidth}
formatOptionLabel={props.formatOptionLabel ? formatOptionLabel : undefined}
components={{ DropdownIndicator: CustomDropdownIndicator, ...props.components }}
diff --git a/components/common/select/Select.tsx b/components/common/select/Select.tsx
index 0764ff4ab..2f24683ad 100644
--- a/components/common/select/Select.tsx
+++ b/components/common/select/Select.tsx
@@ -51,7 +51,12 @@ const Select = >(props: Props null, Option, ...props.components }}
+ components={{
+ IndicatorSeparator: null,
+ ClearIndicator: () => null,
+ Option,
+ ...props.components,
+ }}
classNames={{
control: (state) =>
twMerge(
diff --git a/components/header/WalletIndicator.tsx b/components/header/WalletIndicator.tsx
index bd9cb6efb..0e7a1ef26 100644
--- a/components/header/WalletIndicator.tsx
+++ b/components/header/WalletIndicator.tsx
@@ -1,6 +1,7 @@
'use client';
import { useMounted } from 'lib/hooks/useMounted';
+import { useCallback } from 'react';
import { useAccount, useSwitchChain } from 'wagmi';
import ChainSelect from '../common/select/ChainSelect';
import WalletIndicatorDropdown from './WalletIndicatorDropdown';
@@ -17,18 +18,14 @@ const WalletIndicator = ({ menuAlign, size, style, className }: Props) => {
const isMounted = useMounted();
const { address: account, chain } = useAccount();
const { switchChain } = useSwitchChain();
+ const onSelect = useCallback((chainId: number) => switchChain({ chainId }), [switchChain]);
if (!isMounted) return null;
return (
{account && (
- switchChain({ chainId })}
- selected={chain?.id}
- menuAlign={menuAlign}
- />
+
)}