diff --git a/packages/instant/src/components/amount_placeholder.tsx b/packages/instant/src/components/amount_placeholder.tsx index 29ce8fafb2..290e34a070 100644 --- a/packages/instant/src/components/amount_placeholder.tsx +++ b/packages/instant/src/components/amount_placeholder.tsx @@ -30,3 +30,5 @@ export const AmountPlaceholder: React.StatelessComponent return ; } }; + +AmountPlaceholder.displayName = 'AmountPlaceholder'; diff --git a/packages/instant/src/components/animations/slide_animation.tsx b/packages/instant/src/components/animations/slide_animation.tsx index 5992bcba78..6ac47e9a66 100644 --- a/packages/instant/src/components/animations/slide_animation.tsx +++ b/packages/instant/src/components/animations/slide_animation.tsx @@ -11,6 +11,7 @@ export interface SlideAnimationProps { slideOutSettings: OptionallyScreenSpecific; zIndex?: OptionallyScreenSpecific; height?: string; + onAnimationEnd?: () => void; } export const SlideAnimation: React.StatelessComponent = props => { @@ -19,8 +20,15 @@ export const SlideAnimation: React.StatelessComponent = pro } const positionSettings = props.animationState === 'slidIn' ? props.slideInSettings : props.slideOutSettings; return ( - + {props.children} ); }; + +SlideAnimation.displayName = 'SlideAnimation'; diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx index 5c9c28ae49..551e857a5b 100644 --- a/packages/instant/src/components/buy_button.tsx +++ b/packages/instant/src/components/buy_button.tsx @@ -32,7 +32,7 @@ export interface BuyButtonProps { onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void; } -export class BuyButton extends React.Component { +export class BuyButton extends React.PureComponent { public static defaultProps = { onClick: util.boundNoop, onBuySuccess: util.boundNoop, diff --git a/packages/instant/src/components/buy_order_progress.tsx b/packages/instant/src/components/buy_order_progress.tsx index a19f5a4d08..11ac5d5e01 100644 --- a/packages/instant/src/components/buy_order_progress.tsx +++ b/packages/instant/src/components/buy_order_progress.tsx @@ -31,3 +31,5 @@ export const BuyOrderProgress: React.StatelessComponent = } return null; }; + +BuyOrderProgress.displayName = 'BuyOrderProgress'; diff --git a/packages/instant/src/components/buy_order_state_buttons.tsx b/packages/instant/src/components/buy_order_state_buttons.tsx index 833818900b..1214559d16 100644 --- a/packages/instant/src/components/buy_order_state_buttons.tsx +++ b/packages/instant/src/components/buy_order_state_buttons.tsx @@ -71,3 +71,5 @@ export const BuyOrderStateButtons: React.StatelessComponent ); }; + +BuyOrderStateButtons.displayName = 'BuyOrderStateButtons'; diff --git a/packages/instant/src/components/erc20_asset_amount_input.tsx b/packages/instant/src/components/erc20_asset_amount_input.tsx index 4da82eb739..0418f9165c 100644 --- a/packages/instant/src/components/erc20_asset_amount_input.tsx +++ b/packages/instant/src/components/erc20_asset_amount_input.tsx @@ -31,7 +31,7 @@ export interface ERC20AssetAmountInputState { currentFontSizePx: number; } -export class ERC20AssetAmountInput extends React.Component { +export class ERC20AssetAmountInput extends React.PureComponent { public static defaultProps = { onChange: util.boundNoop, isDisabled: false, diff --git a/packages/instant/src/components/erc20_token_selector.tsx b/packages/instant/src/components/erc20_token_selector.tsx index cb8a8c797a..a26fb5cf51 100644 --- a/packages/instant/src/components/erc20_token_selector.tsx +++ b/packages/instant/src/components/erc20_token_selector.tsx @@ -21,12 +21,12 @@ export interface ERC20TokenSelectorState { searchQuery: string; } -export class ERC20TokenSelector extends React.Component { +export class ERC20TokenSelector extends React.PureComponent { public state: ERC20TokenSelectorState = { searchQuery: '', }; public render(): React.ReactNode { - const { tokens, onTokenSelect } = this.props; + const { tokens } = this.props; return ( @@ -42,12 +42,11 @@ export class ERC20TokenSelector extends React.Component tabIndex={-1} /> - {_.map(tokens, token => { - if (!this._isTokenQueryMatch(token)) { - return null; - } - return ; - })} + ); @@ -59,8 +58,32 @@ export class ERC20TokenSelector extends React.Component }); analytics.trackTokenSelectorSearched(searchQuery); }; + private readonly _handleTokenClick = (token: ERC20Asset): void => { + this.props.onTokenSelect(token); + }; +} + +interface TokenRowFilterProps { + tokens: ERC20Asset[]; + onClick: (token: ERC20Asset) => void; + searchQuery: string; +} + +class TokenRowFilter extends React.Component { + public render(): React.ReactNode { + return _.map(this.props.tokens, token => { + if (!this._isTokenQueryMatch(token)) { + return null; + } + return ; + }); + } + public shouldComponentUpdate(nextProps: TokenRowFilterProps): boolean { + const arePropsDeeplyEqual = _.isEqual(nextProps, this.props); + return !arePropsDeeplyEqual; + } private readonly _isTokenQueryMatch = (token: ERC20Asset): boolean => { - const { searchQuery } = this.state; + const { searchQuery } = this.props; const searchQueryLowerCase = searchQuery.toLowerCase().trim(); if (searchQueryLowerCase === '') { return true; @@ -76,7 +99,7 @@ interface TokenSelectorRowProps { onClick: (token: ERC20Asset) => void; } -class TokenSelectorRow extends React.Component { +class TokenSelectorRow extends React.PureComponent { public render(): React.ReactNode { const { token } = this.props; const circleColor = token.metaData.primaryColor || 'black'; @@ -131,21 +154,23 @@ const getTokenIcon = (symbol: string): React.StatelessComponent | undefined => { } }; -const TokenSelectorRowIcon: React.StatelessComponent = props => { - const { token } = props; - const iconUrlIfExists = token.metaData.iconUrl; +class TokenSelectorRowIcon extends React.PureComponent { + public render(): React.ReactNode { + const { token } = this.props; + const iconUrlIfExists = token.metaData.iconUrl; - const TokenIcon = getTokenIcon(token.metaData.symbol); - const displaySymbol = assetUtils.bestNameForAsset(token); - if (!_.isUndefined(iconUrlIfExists)) { - return ; - } else if (!_.isUndefined(TokenIcon)) { - return ; - } else { - return ( - - {displaySymbol} - - ); + const TokenIcon = getTokenIcon(token.metaData.symbol); + const displaySymbol = assetUtils.bestNameForAsset(token); + if (!_.isUndefined(iconUrlIfExists)) { + return ; + } else if (!_.isUndefined(TokenIcon)) { + return ; + } else { + return ( + + {displaySymbol} + + ); + } } -}; +} diff --git a/packages/instant/src/components/install_wallet_panel_content.tsx b/packages/instant/src/components/install_wallet_panel_content.tsx index 481d82da0c..1af3dafbbc 100644 --- a/packages/instant/src/components/install_wallet_panel_content.tsx +++ b/packages/instant/src/components/install_wallet_panel_content.tsx @@ -18,7 +18,7 @@ import { Button } from './ui/button'; export interface InstallWalletPanelContentProps {} -export class InstallWalletPanelContent extends React.Component { +export class InstallWalletPanelContent extends React.PureComponent { public render(): React.ReactNode { const panelProps = this._getStandardPanelContentProps(); return ; diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 5b1f9592dc..e943f68d7d 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -28,7 +28,7 @@ const ICON_WIDTH = 34; const ICON_HEIGHT = 34; const ICON_COLOR = ColorOption.white; -export class InstantHeading extends React.Component { +export class InstantHeading extends React.PureComponent { public render(): React.ReactNode { const iconOrAmounts = this._renderIcon() || this._renderAmountsSection(); return ( diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 9c10ef9e69..4db20b13e1 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -26,7 +26,7 @@ export interface OrderDetailsProps { onBaseCurrencySwitchEth: () => void; onBaseCurrencySwitchUsd: () => void; } -export class OrderDetails extends React.Component { +export class OrderDetails extends React.PureComponent { public render(): React.ReactNode { const shouldShowUsdError = this.props.baseCurrency === BaseCurrency.USD && this._hadErrorFetchingUsdPrice(); return ( @@ -200,7 +200,7 @@ export interface OrderDetailsRowProps { primaryValue: React.ReactNode; secondaryValue?: React.ReactNode; } -export class OrderDetailsRow extends React.Component { +export class OrderDetailsRow extends React.PureComponent { public render(): React.ReactNode { return ( diff --git a/packages/instant/src/components/payment_method.tsx b/packages/instant/src/components/payment_method.tsx index abadf4bd67..ada9f7bab5 100644 --- a/packages/instant/src/components/payment_method.tsx +++ b/packages/instant/src/components/payment_method.tsx @@ -24,7 +24,7 @@ export interface PaymentMethodProps { onUnlockWalletClick: () => void; } -export class PaymentMethod extends React.Component { +export class PaymentMethod extends React.PureComponent { public render(): React.ReactNode { return ( diff --git a/packages/instant/src/components/payment_method_dropdown.tsx b/packages/instant/src/components/payment_method_dropdown.tsx index 872ac0831d..e463e3eaef 100644 --- a/packages/instant/src/components/payment_method_dropdown.tsx +++ b/packages/instant/src/components/payment_method_dropdown.tsx @@ -16,7 +16,7 @@ export interface PaymentMethodDropdownProps { network: Network; } -export class PaymentMethodDropdown extends React.Component { +export class PaymentMethodDropdown extends React.PureComponent { public render(): React.ReactNode { const { accountAddress, accountEthBalanceInWei } = this.props; const value = format.ethAddress(accountAddress); diff --git a/packages/instant/src/components/placing_order_button.tsx b/packages/instant/src/components/placing_order_button.tsx index 2516b90b12..528a305dca 100644 --- a/packages/instant/src/components/placing_order_button.tsx +++ b/packages/instant/src/components/placing_order_button.tsx @@ -14,3 +14,5 @@ export const PlacingOrderButton: React.StatelessComponent<{}> = props => ( Placing Order… ); + +PlacingOrderButton.displayName = 'PlacingOrderButton'; diff --git a/packages/instant/src/components/scaling_amount_input.tsx b/packages/instant/src/components/scaling_amount_input.tsx index 4feb0502db..7dc1fdc0c0 100644 --- a/packages/instant/src/components/scaling_amount_input.tsx +++ b/packages/instant/src/components/scaling_amount_input.tsx @@ -26,7 +26,7 @@ interface ScalingAmountInputState { } const { stringToMaybeBigNumber, areMaybeBigNumbersEqual } = maybeBigNumberUtil; -export class ScalingAmountInput extends React.Component { +export class ScalingAmountInput extends React.PureComponent { public static defaultProps = { onAmountChange: util.boundNoop, onFontSizeChange: util.boundNoop, diff --git a/packages/instant/src/components/scaling_input.tsx b/packages/instant/src/components/scaling_input.tsx index 00aea37dac..c31de1fb5b 100644 --- a/packages/instant/src/components/scaling_input.tsx +++ b/packages/instant/src/components/scaling_input.tsx @@ -51,7 +51,7 @@ const defaultScalingSettings: ScalingSettings = { additionalInputSpaceInCh: 0.4, }; -export class ScalingInput extends React.Component { +export class ScalingInput extends React.PureComponent { public static defaultProps = { onChange: util.boundNoop, onFontSizeChange: util.boundNoop, diff --git a/packages/instant/src/components/secondary_button.tsx b/packages/instant/src/components/secondary_button.tsx index 705390e283..0714ce2870 100644 --- a/packages/instant/src/components/secondary_button.tsx +++ b/packages/instant/src/components/secondary_button.tsx @@ -24,3 +24,4 @@ export const SecondaryButton: React.StatelessComponent = p SecondaryButton.defaultProps = { width: '100%', }; +SecondaryButton.displayName = 'SecondaryButton'; diff --git a/packages/instant/src/components/section_header.tsx b/packages/instant/src/components/section_header.tsx index d0974ebdc2..2185b67ba0 100644 --- a/packages/instant/src/components/section_header.tsx +++ b/packages/instant/src/components/section_header.tsx @@ -18,3 +18,4 @@ export const SectionHeader: React.StatelessComponent = props ); }; +SectionHeader.displayName = 'SectionHeader'; diff --git a/packages/instant/src/components/sliding_error.tsx b/packages/instant/src/components/sliding_error.tsx index b59e2a905c..c7c6732cfc 100644 --- a/packages/instant/src/components/sliding_error.tsx +++ b/packages/instant/src/components/sliding_error.tsx @@ -38,6 +38,8 @@ export const Error: React.StatelessComponent = props => ( ); +Error.displayName = 'Error'; + export interface SlidingErrorProps extends ErrorProps { animationState: SlideAnimationState; } @@ -94,3 +96,5 @@ export const SlidingError: React.StatelessComponent = props = ); }; + +SlidingError.displayName = 'SlidingError'; diff --git a/packages/instant/src/components/sliding_panel.tsx b/packages/instant/src/components/sliding_panel.tsx index 7f90370499..9b09a0d804 100644 --- a/packages/instant/src/components/sliding_panel.tsx +++ b/packages/instant/src/components/sliding_panel.tsx @@ -26,42 +26,48 @@ export const Panel: React.StatelessComponent = ({ children, onClose ); +Panel.displayName = 'Panel'; + export interface SlidingPanelProps extends PanelProps { animationState: SlideAnimationState; + onAnimationEnd?: () => void; } -export const SlidingPanel: React.StatelessComponent = props => { - if (props.animationState === 'none') { - return null; +export class SlidingPanel extends React.PureComponent { + public render(): React.ReactNode { + if (this.props.animationState === 'none') { + return null; + } + const { animationState, onAnimationEnd, ...rest } = this.props; + const slideAmount = '100%'; + const slideUpSettings: PositionAnimationSettings = { + duration: '0.3s', + timingFunction: 'ease-in-out', + top: { + from: slideAmount, + to: '0px', + }, + position: 'absolute', + }; + const slideDownSettings: PositionAnimationSettings = { + duration: '0.3s', + timingFunction: 'ease-out', + top: { + from: '0px', + to: slideAmount, + }, + position: 'absolute', + }; + return ( + + + + ); } - const { animationState, ...rest } = props; - const slideAmount = '100%'; - const slideUpSettings: PositionAnimationSettings = { - duration: '0.3s', - timingFunction: 'ease-in-out', - top: { - from: slideAmount, - to: '0px', - }, - position: 'absolute', - }; - const slideDownSettings: PositionAnimationSettings = { - duration: '0.3s', - timingFunction: 'ease-out', - top: { - from: '0px', - to: slideAmount, - }, - position: 'absolute', - }; - return ( - - - - ); -}; +} diff --git a/packages/instant/src/components/standard_panel_content.tsx b/packages/instant/src/components/standard_panel_content.tsx index 79b7bff24b..f2987df82f 100644 --- a/packages/instant/src/components/standard_panel_content.tsx +++ b/packages/instant/src/components/standard_panel_content.tsx @@ -71,3 +71,5 @@ export const StandardPanelContent: React.StatelessComponent{action} ); + +StandardPanelContent.displayName = 'StandardPanelContent'; diff --git a/packages/instant/src/components/standard_sliding_panel.tsx b/packages/instant/src/components/standard_sliding_panel.tsx index 9f517d273c..bcc9d3dcec 100644 --- a/packages/instant/src/components/standard_sliding_panel.tsx +++ b/packages/instant/src/components/standard_sliding_panel.tsx @@ -9,7 +9,7 @@ export interface StandardSlidingPanelProps extends StandardSlidingPanelSettings onClose: () => void; } -export class StandardSlidingPanel extends React.Component { +export class StandardSlidingPanel extends React.PureComponent { public render(): React.ReactNode { const { animationState, content, onClose } = this.props; return ( diff --git a/packages/instant/src/components/time_counter.tsx b/packages/instant/src/components/time_counter.tsx index f9b68163c3..93dc497d5e 100644 --- a/packages/instant/src/components/time_counter.tsx +++ b/packages/instant/src/components/time_counter.tsx @@ -16,7 +16,7 @@ interface TimeCounterState { elapsedSeconds: number; } -export class TimeCounter extends React.Component { +export class TimeCounter extends React.PureComponent { public state = { elapsedSeconds: 0, }; diff --git a/packages/instant/src/components/timed_progress_bar.tsx b/packages/instant/src/components/timed_progress_bar.tsx index fb3927088f..b1644b871d 100644 --- a/packages/instant/src/components/timed_progress_bar.tsx +++ b/packages/instant/src/components/timed_progress_bar.tsx @@ -17,7 +17,7 @@ export interface TimedProgressBarProps { * Goes from 0% -> PROGRESS_STALL_AT_WIDTH over time of expectedTimeMs * When hasEnded set to true, goes to 100% through animation of PROGRESS_FINISH_ANIMATION_TIME_MS length of time */ -export class TimedProgressBar extends React.Component { +export class TimedProgressBar extends React.PureComponent { private readonly _barRef = React.createRef(); public render(): React.ReactNode { diff --git a/packages/instant/src/components/ui/dropdown.tsx b/packages/instant/src/components/ui/dropdown.tsx index 02e87d6394..93ad06aee6 100644 --- a/packages/instant/src/components/ui/dropdown.tsx +++ b/packages/instant/src/components/ui/dropdown.tsx @@ -26,7 +26,7 @@ export interface DropdownState { isOpen: boolean; } -export class Dropdown extends React.Component { +export class Dropdown extends React.PureComponent { public static defaultProps = { items: [], }; @@ -138,3 +138,5 @@ export const DropdownItem: React.StatelessComponent = ({ text ); + +DropdownItem.displayName = 'DropdownItem'; diff --git a/packages/instant/src/components/wallet_prompt.tsx b/packages/instant/src/components/wallet_prompt.tsx index c07cfe7b59..10433767f0 100644 --- a/packages/instant/src/components/wallet_prompt.tsx +++ b/packages/instant/src/components/wallet_prompt.tsx @@ -45,3 +45,5 @@ WalletPrompt.defaultProps = { primaryColor: ColorOption.darkOrange, secondaryColor: ColorOption.lightOrange, }; + +WalletPrompt.displayName = 'WalletPrompt'; diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx index 2267b4dbf2..e9cb48e61a 100644 --- a/packages/instant/src/components/zero_ex_instant.tsx +++ b/packages/instant/src/components/zero_ex_instant.tsx @@ -17,3 +17,5 @@ export const ZeroExInstant: React.StatelessComponent = props ); }; + +ZeroExInstant.displayName = 'ZeroExInstant'; diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx index 0337c77144..e8c64d5d10 100644 --- a/packages/instant/src/components/zero_ex_instant_container.tsx +++ b/packages/instant/src/components/zero_ex_instant_container.tsx @@ -24,7 +24,10 @@ export interface ZeroExInstantContainerState { tokenSelectionPanelAnimationState: SlideAnimationState; } -export class ZeroExInstantContainer extends React.Component { +export class ZeroExInstantContainer extends React.PureComponent< + ZeroExInstantContainerProps, + ZeroExInstantContainerState +> { public state = { tokenSelectionPanelAnimationState: 'none' as SlideAnimationState, }; @@ -60,6 +63,7 @@ export class ZeroExInstantContainer extends React.Component @@ -98,4 +102,11 @@ export class ZeroExInstantContainer extends React.Component { + if (this.state.tokenSelectionPanelAnimationState === 'slidOut') { + // When the slidOut animation completes, don't keep the panel mounted. + // Performance optimization + this.setState({ tokenSelectionPanelAnimationState: 'none' }); + } + }; } diff --git a/packages/instant/src/components/zero_ex_instant_overlay.tsx b/packages/instant/src/components/zero_ex_instant_overlay.tsx index 96e5606917..38a7160912 100644 --- a/packages/instant/src/components/zero_ex_instant_overlay.tsx +++ b/packages/instant/src/components/zero_ex_instant_overlay.tsx @@ -49,3 +49,5 @@ export const ZeroExInstantOverlay: React.StatelessComponent ); }; + +ZeroExInstantOverlay.displayName = 'ZeroExInstantOverlay'; diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index 2de327cd76..ec8e82ee3c 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -21,7 +21,7 @@ import { providerStateFactory } from '../util/provider_state_factory'; export type ZeroExInstantProviderProps = ZeroExInstantBaseConfig; -export class ZeroExInstantProvider extends React.Component { +export class ZeroExInstantProvider extends React.PureComponent { private readonly _store: Store; private _accountUpdateHeartbeat?: Heartbeater; private _buyQuoteHeartbeat?: Heartbeater; diff --git a/packages/instant/src/containers/latest_error.tsx b/packages/instant/src/containers/latest_error.tsx index 0d4349124a..6da4558ef1 100644 --- a/packages/instant/src/containers/latest_error.tsx +++ b/packages/instant/src/containers/latest_error.tsx @@ -14,7 +14,7 @@ import { zIndex } from '../style/z_index'; import { Asset, DisplayStatus, Omit, SlideAnimationState } from '../types'; import { errorFlasher } from '../util/error_flasher'; -export interface LatestErrorComponentProps { +interface LatestErrorComponentProps { asset?: Asset; latestErrorMessage?: string; animationState: SlideAnimationState; @@ -22,7 +22,7 @@ export interface LatestErrorComponentProps { onOverlayClick: () => void; } -export const LatestErrorComponent: React.StatelessComponent = props => { +const LatestErrorComponent: React.StatelessComponent = props => { if (!props.latestErrorMessage) { // Render a hidden SlidingError such that instant does not move when a real error is rendered. return (