From 7b41838163b64f2fa7cf2dd8195b258027487abd Mon Sep 17 00:00:00 2001 From: tyleroooo Date: Thu, 21 Nov 2024 13:57:12 -0500 Subject: [PATCH] fix: forwardRef to fix console errors (#1299) --- src/components/visx/SparklineChart.tsx | 92 ++++----- src/components/visx/TimeSeriesChart.tsx | 4 + src/views/tables/FillsTable.tsx | 148 +++++++------- src/views/tables/OrdersTable.tsx | 182 +++++++++--------- src/views/tables/PositionsTable.tsx | 244 ++++++++++++------------ 5 files changed, 341 insertions(+), 329 deletions(-) diff --git a/src/components/visx/SparklineChart.tsx b/src/components/visx/SparklineChart.tsx index d6a8ea356..9080dd759 100644 --- a/src/components/visx/SparklineChart.tsx +++ b/src/components/visx/SparklineChart.tsx @@ -25,51 +25,53 @@ export const SparklineChart = (props: SparklineChartProps {/* eslint-disable-next-line react/no-unused-prop-types */} - {({ height, width }: { width: number; height: number }) => ( - - - - - - - - )} + {({ height, width }: { width: number; height: number }) => + height > 0 && width > 0 ? ( + + + + + + + + ) : null + } ); }; diff --git a/src/components/visx/TimeSeriesChart.tsx b/src/components/visx/TimeSeriesChart.tsx index c48507b13..fd8421032 100644 --- a/src/components/visx/TimeSeriesChart.tsx +++ b/src/components/visx/TimeSeriesChart.tsx @@ -318,6 +318,10 @@ export const TimeSeriesChart = ({ const numTicksY = (height - (margin?.top ?? 0) - (margin?.bottom ?? 0)) / tickSpacingY; + if (width === 0 || height === 0) { + return null; + } + return ( { - const stringGetter = useStringGetter(); - const dispatch = useAppDispatch(); - const { isMobile } = useBreakpoints(); +export const FillsTable = forwardRef( + ({ + columnKeys, + columnWidths, + currentMarket, + initialPageSize, + withGradientCardRows, + withOuterBorder, + withInnerBorders = true, + }: ElementProps & StyleProps) => { + const stringGetter = useStringGetter(); + const dispatch = useAppDispatch(); + const { isMobile } = useBreakpoints(); - const marketFills = useAppSelector(getCurrentMarketFills, shallowEqual) ?? EMPTY_ARR; - const allFills = useAppSelector(getSubaccountFills, shallowEqual) ?? EMPTY_ARR; - const fills = currentMarket ? marketFills : allFills; + const marketFills = useAppSelector(getCurrentMarketFills, shallowEqual) ?? EMPTY_ARR; + const allFills = useAppSelector(getSubaccountFills, shallowEqual) ?? EMPTY_ARR; + const fills = currentMarket ? marketFills : allFills; - const allPerpetualMarkets = orEmptyRecord(useAppSelector(getPerpetualMarkets, shallowEqual)); - const allAssets = orEmptyRecord(useAppSelector(getAssets, shallowEqual)); + const allPerpetualMarkets = orEmptyRecord(useAppSelector(getPerpetualMarkets, shallowEqual)); + const allAssets = orEmptyRecord(useAppSelector(getAssets, shallowEqual)); - useEffect(() => { - // marked fills as seen both on mount and dismount (i.e. new fill came in while fills table is being shown) - dispatch(viewedFills(currentMarket)); - return () => { + useEffect(() => { + // marked fills as seen both on mount and dismount (i.e. new fill came in while fills table is being shown) dispatch(viewedFills(currentMarket)); - }; - }, [currentMarket, dispatch]); + return () => { + dispatch(viewedFills(currentMarket)); + }; + }, [currentMarket, dispatch]); - const symbol = mapIfPresent(currentMarket, (market) => - mapIfPresent(allPerpetualMarkets[market]?.assetId, (assetId) => allAssets[assetId]?.id) - ); + const symbol = mapIfPresent(currentMarket, (market) => + mapIfPresent(allPerpetualMarkets[market]?.assetId, (assetId) => allAssets[assetId]?.id) + ); - const fillsData = useMemo( - () => - fills.map( - (fill: SubaccountFill): FillTableRow => - getHydratedTradingData({ - data: fill, - assets: allAssets, - perpetualMarkets: allPerpetualMarkets, - }) - ), - [fills, allPerpetualMarkets, allAssets] - ); + const fillsData = useMemo( + () => + fills.map( + (fill: SubaccountFill): FillTableRow => + getHydratedTradingData({ + data: fill, + assets: allAssets, + perpetualMarkets: allPerpetualMarkets, + }) + ), + [fills, allPerpetualMarkets, allAssets] + ); - return ( - <$Table - key={currentMarket ?? 'all-fills'} - label="Fills" - data={ - isMobile && withGradientCardRows ? fillsData.slice(0, MOBILE_FILLS_PER_PAGE) : fillsData - } - getRowKey={(row: FillTableRow) => row.id} - onRowAction={(key: Key) => - dispatch(openDialog(DialogTypes.FillDetails({ fillId: `${key}` }))) - } - columns={columnKeys.map((key: FillsTableColumnKey) => - getFillsTableColumnDef({ - key, - stringGetter, - symbol, - width: columnWidths?.[key], - }) - )} - slotEmpty={ - <> - -

{stringGetter({ key: STRING_KEYS.TRADES_EMPTY_STATE })}

- - } - initialPageSize={initialPageSize} - withOuterBorder={withOuterBorder} - withInnerBorders={withInnerBorders} - withScrollSnapColumns - withScrollSnapRows - withFocusStickyRows - /> - ); -}; + return ( + <$Table + key={currentMarket ?? 'all-fills'} + label="Fills" + data={ + isMobile && withGradientCardRows ? fillsData.slice(0, MOBILE_FILLS_PER_PAGE) : fillsData + } + getRowKey={(row: FillTableRow) => row.id} + onRowAction={(key: Key) => + dispatch(openDialog(DialogTypes.FillDetails({ fillId: `${key}` }))) + } + columns={columnKeys.map((key: FillsTableColumnKey) => + getFillsTableColumnDef({ + key, + stringGetter, + symbol, + width: columnWidths?.[key], + }) + )} + slotEmpty={ + <> + +

{stringGetter({ key: STRING_KEYS.TRADES_EMPTY_STATE })}

+ + } + initialPageSize={initialPageSize} + withOuterBorder={withOuterBorder} + withInnerBorders={withInnerBorders} + withScrollSnapColumns + withScrollSnapRows + withFocusStickyRows + /> + ); + } +); const $Table = styled(Table)` ${tradeViewMixins.horizontalTable} ` as typeof Table; diff --git a/src/views/tables/OrdersTable.tsx b/src/views/tables/OrdersTable.tsx index 9ccaef944..99110ea2e 100644 --- a/src/views/tables/OrdersTable.tsx +++ b/src/views/tables/OrdersTable.tsx @@ -1,4 +1,4 @@ -import { Key, ReactNode, useEffect, useMemo } from 'react'; +import { forwardRef, Key, ReactNode, useEffect, useMemo } from 'react'; import { OrderSide } from '@dydxprotocol/v4-client-js'; import { ColumnSize } from '@react-types/table'; @@ -406,96 +406,98 @@ type StyleProps = { withOuterBorder?: boolean; }; -export const OrdersTable = ({ - columnKeys = [], - columnWidths, - currentMarket, - marketTypeFilter, - initialPageSize, - withOuterBorder, -}: ElementProps & StyleProps) => { - const stringGetter = useStringGetter(); - const dispatch = useAppDispatch(); - const { isTablet } = useBreakpoints(); - - const isAccountViewOnly = useAppSelector(calculateIsAccountViewOnly); - const marketOrders = useAppSelector(getCurrentMarketOrders, shallowEqual) ?? EMPTY_ARR; - const allOrders = useAppSelector(getSubaccountUnclearedOrders, shallowEqual) ?? EMPTY_ARR; - - const orders = useMemo( - () => - (currentMarket ? marketOrders : allOrders).filter((order) => { - const orderType = getMarginModeFromSubaccountNumber(order.subaccountNumber).name; - return marketTypeMatchesFilter(orderType, marketTypeFilter); - }), - [allOrders, currentMarket, marketOrders, marketTypeFilter] - ); - - const allPerpetualMarkets = orEmptyRecord(useAppSelector(getPerpetualMarkets, shallowEqual)); - const allAssets = orEmptyRecord(useAppSelector(getAssets, shallowEqual)); - - const hasUnseenOrderUpdates = useAppSelector(getHasUnseenOrderUpdates); - - useEffect(() => { - if (hasUnseenOrderUpdates) dispatch(viewedOrders()); - }, [hasUnseenOrderUpdates]); - - const symbol = mapIfPresent(currentMarket, (market) => - mapIfPresent(allPerpetualMarkets[market]?.assetId, (assetId) => allAssets[assetId]?.id) - ); - - const ordersData = useMemo( - () => - orders.map( - (order: SubaccountOrder): OrderTableRow => - getHydratedTradingData({ - data: order, - assets: allAssets, - perpetualMarkets: allPerpetualMarkets, +export const OrdersTable = forwardRef( + ({ + columnKeys = [], + columnWidths, + currentMarket, + marketTypeFilter, + initialPageSize, + withOuterBorder, + }: ElementProps & StyleProps) => { + const stringGetter = useStringGetter(); + const dispatch = useAppDispatch(); + const { isTablet } = useBreakpoints(); + + const isAccountViewOnly = useAppSelector(calculateIsAccountViewOnly); + const marketOrders = useAppSelector(getCurrentMarketOrders, shallowEqual) ?? EMPTY_ARR; + const allOrders = useAppSelector(getSubaccountUnclearedOrders, shallowEqual) ?? EMPTY_ARR; + + const orders = useMemo( + () => + (currentMarket ? marketOrders : allOrders).filter((order) => { + const orderType = getMarginModeFromSubaccountNumber(order.subaccountNumber).name; + return marketTypeMatchesFilter(orderType, marketTypeFilter); + }), + [allOrders, currentMarket, marketOrders, marketTypeFilter] + ); + + const allPerpetualMarkets = orEmptyRecord(useAppSelector(getPerpetualMarkets, shallowEqual)); + const allAssets = orEmptyRecord(useAppSelector(getAssets, shallowEqual)); + + const hasUnseenOrderUpdates = useAppSelector(getHasUnseenOrderUpdates); + + useEffect(() => { + if (hasUnseenOrderUpdates) dispatch(viewedOrders()); + }, [hasUnseenOrderUpdates]); + + const symbol = mapIfPresent(currentMarket, (market) => + mapIfPresent(allPerpetualMarkets[market]?.assetId, (assetId) => allAssets[assetId]?.id) + ); + + const ordersData = useMemo( + () => + orders.map( + (order: SubaccountOrder): OrderTableRow => + getHydratedTradingData({ + data: order, + assets: allAssets, + perpetualMarkets: allPerpetualMarkets, + }) + ), + [orders, allPerpetualMarkets, allAssets] + ); + + return ( + <$Table + key={currentMarket ?? 'all-orders'} + label="Orders" + data={ordersData} + getRowKey={(row: OrderTableRow) => row.id} + getRowAttributes={(row: OrderTableRow) => ({ + 'data-clearable': isOrderStatusClearable(row.status), + })} + onRowAction={(key: Key) => + dispatch(openDialog(DialogTypes.OrderDetails({ orderId: `${key}` }))) + } + columns={columnKeys.map((key: OrdersTableColumnKey) => + getOrdersTableColumnDef({ + key, + currentMarket, + dispatch, + isTablet, + stringGetter, + symbol, + isAccountViewOnly, + width: columnWidths?.[key], }) - ), - [orders, allPerpetualMarkets, allAssets] - ); - - return ( - <$Table - key={currentMarket ?? 'all-orders'} - label="Orders" - data={ordersData} - getRowKey={(row: OrderTableRow) => row.id} - getRowAttributes={(row: OrderTableRow) => ({ - 'data-clearable': isOrderStatusClearable(row.status), - })} - onRowAction={(key: Key) => - dispatch(openDialog(DialogTypes.OrderDetails({ orderId: `${key}` }))) - } - columns={columnKeys.map((key: OrdersTableColumnKey) => - getOrdersTableColumnDef({ - key, - currentMarket, - dispatch, - isTablet, - stringGetter, - symbol, - isAccountViewOnly, - width: columnWidths?.[key], - }) - )} - slotEmpty={ - <> - -

{stringGetter({ key: STRING_KEYS.ORDERS_EMPTY_STATE })}

- - } - initialPageSize={initialPageSize} - withOuterBorder={withOuterBorder} - withInnerBorders - withScrollSnapColumns - withScrollSnapRows - withFocusStickyRows - /> - ); -}; + )} + slotEmpty={ + <> + +

{stringGetter({ key: STRING_KEYS.ORDERS_EMPTY_STATE })}

+ + } + initialPageSize={initialPageSize} + withOuterBorder={withOuterBorder} + withInnerBorders + withScrollSnapColumns + withScrollSnapRows + withFocusStickyRows + /> + ); + } +); const $Table = styled(Table)` ${tradeViewMixins.horizontalTable} diff --git a/src/views/tables/PositionsTable.tsx b/src/views/tables/PositionsTable.tsx index 805c4c7f4..f80e4eabf 100644 --- a/src/views/tables/PositionsTable.tsx +++ b/src/views/tables/PositionsTable.tsx @@ -1,4 +1,4 @@ -import { Key, useMemo } from 'react'; +import { forwardRef, Key, useMemo } from 'react'; import { Separator } from '@radix-ui/react-separator'; import type { ColumnSize } from '@react-types/table'; @@ -42,7 +42,7 @@ import { getAssets } from '@/state/assetsSelectors'; import { getPerpetualMarkets } from '@/state/perpetualsSelectors'; import { getDisplayableAssetFromBaseAsset } from '@/lib/assetUtils'; -import { MustBigNumber, getNumberSign } from '@/lib/numbers'; +import { getNumberSign, MustBigNumber } from '@/lib/numbers'; import { safeAssign } from '@/lib/objectHelpers'; import { testFlags } from '@/lib/testFlags'; import { getMarginModeFromSubaccountNumber, getPositionMargin } from '@/lib/tradeData'; @@ -616,125 +616,127 @@ type StyleProps = { withOuterBorder?: boolean; }; -export const PositionsTable = ({ - columnKeys, - columnWidths, - currentRoute, - currentMarket, - marketTypeFilter, - showClosePositionAction, - initialPageSize, - onNavigate, - navigateToOrders, - withGradientCardRows, - withOuterBorder, -}: ElementProps & StyleProps) => { - const stringGetter = useStringGetter(); - const navigate = useNavigate(); - const { isSlTpLimitOrdersEnabled } = useEnvFeatures(); - const { uiRefresh } = testFlags; - const { isTablet } = useBreakpoints(); - - const isAccountViewOnly = useAppSelector(calculateIsAccountViewOnly); - const perpetualMarkets = orEmptyRecord(useAppSelector(getPerpetualMarkets, shallowEqual)); - const assets = orEmptyRecord(useAppSelector(getAssets, shallowEqual)); - - const openPositions = useAppSelector(getExistingOpenPositions, shallowEqual) ?? EMPTY_ARR; - const positions = useMemo(() => { - return openPositions.filter((position) => { - const matchesMarket = currentMarket == null || position.id === currentMarket; - const subaccountNumber = position.childSubaccountNumber; - const marginType = getMarginModeFromSubaccountNumber(subaccountNumber).name; - const matchesType = marketTypeMatchesFilter(marginType, marketTypeFilter); - return matchesMarket && matchesType; - }); - }, [currentMarket, marketTypeFilter, openPositions]); - - const conditionalOrderSelector = useMemo(getSubaccountConditionalOrders, []); - const { stopLossOrders: allStopLossOrders, takeProfitOrders: allTakeProfitOrders } = - useAppSelector((s) => conditionalOrderSelector(s, isSlTpLimitOrdersEnabled), { - equalityFn: (oldVal, newVal) => { - return ( - shallowEqual(oldVal.stopLossOrders, newVal.stopLossOrders) && - shallowEqual(oldVal.takeProfitOrders, newVal.takeProfitOrders) - ); - }, - }); - - const positionsData = useMemo( - () => - positions.map((position: SubaccountPosition): PositionTableRow => { - return safeAssign( - {}, - { - tickSizeDecimals: - perpetualMarkets[position.id]?.configs?.tickSizeDecimals ?? USD_DECIMALS, - asset: assets[position.assetId], - oraclePrice: perpetualMarkets[position.id]?.oraclePrice, - fundingRate: perpetualMarkets[position.id]?.perpetual?.nextFundingRate, - stopLossOrders: allStopLossOrders.filter( - (order: SubaccountOrder) => order.marketId === position.id - ), - takeProfitOrders: allTakeProfitOrders.filter( - (order: SubaccountOrder) => order.marketId === position.id - ), - stepSizeDecimals: - perpetualMarkets[position.id]?.configs?.stepSizeDecimals ?? TOKEN_DECIMALS, - }, - position - ); - }), - [positions, perpetualMarkets, assets, allStopLossOrders, allTakeProfitOrders] - ); - - return ( - <$Table - key={currentMarket ?? 'positions'} - label={stringGetter({ key: STRING_KEYS.POSITIONS })} - data={positionsData} - columns={columnKeys.map((key: PositionsTableColumnKey) => - getPositionsTableColumnDef({ - key, - stringGetter, - width: columnWidths?.[key], - isAccountViewOnly, - showClosePositionAction, - navigateToOrders, - isSinglePosition: positionsData.length === 1, - uiRefresh, - isTablet, - }) - )} - getRowKey={(row: PositionTableRow) => row.id} - onRowAction={ - currentMarket - ? undefined - : (market: Key) => { - navigate(`${AppRoute.Trade}/${market}`, { - state: { from: currentRoute }, - }); - onNavigate?.(); - } - } - getRowAttributes={(row: PositionTableRow) => ({ - 'data-side': row.side.current, - })} - slotEmpty={ - <> - -

{stringGetter({ key: STRING_KEYS.POSITIONS_EMPTY_STATE })}

- - } - initialPageSize={initialPageSize} - withGradientCardRows={withGradientCardRows} - withOuterBorder={withOuterBorder} - withInnerBorders - withScrollSnapColumns - withScrollSnapRows - withFocusStickyRows - /> - ); -}; +export const PositionsTable = forwardRef( + ({ + columnKeys, + columnWidths, + currentRoute, + currentMarket, + marketTypeFilter, + showClosePositionAction, + initialPageSize, + onNavigate, + navigateToOrders, + withGradientCardRows, + withOuterBorder, + }: ElementProps & StyleProps) => { + const stringGetter = useStringGetter(); + const navigate = useNavigate(); + const { isSlTpLimitOrdersEnabled } = useEnvFeatures(); + const { uiRefresh } = testFlags; + const { isTablet } = useBreakpoints(); + + const isAccountViewOnly = useAppSelector(calculateIsAccountViewOnly); + const perpetualMarkets = orEmptyRecord(useAppSelector(getPerpetualMarkets, shallowEqual)); + const assets = orEmptyRecord(useAppSelector(getAssets, shallowEqual)); + + const openPositions = useAppSelector(getExistingOpenPositions, shallowEqual) ?? EMPTY_ARR; + const positions = useMemo(() => { + return openPositions.filter((position) => { + const matchesMarket = currentMarket == null || position.id === currentMarket; + const subaccountNumber = position.childSubaccountNumber; + const marginType = getMarginModeFromSubaccountNumber(subaccountNumber).name; + const matchesType = marketTypeMatchesFilter(marginType, marketTypeFilter); + return matchesMarket && matchesType; + }); + }, [currentMarket, marketTypeFilter, openPositions]); + + const conditionalOrderSelector = useMemo(getSubaccountConditionalOrders, []); + const { stopLossOrders: allStopLossOrders, takeProfitOrders: allTakeProfitOrders } = + useAppSelector((s) => conditionalOrderSelector(s, isSlTpLimitOrdersEnabled), { + equalityFn: (oldVal, newVal) => { + return ( + shallowEqual(oldVal.stopLossOrders, newVal.stopLossOrders) && + shallowEqual(oldVal.takeProfitOrders, newVal.takeProfitOrders) + ); + }, + }); + + const positionsData = useMemo( + () => + positions.map((position: SubaccountPosition): PositionTableRow => { + return safeAssign( + {}, + { + tickSizeDecimals: + perpetualMarkets[position.id]?.configs?.tickSizeDecimals ?? USD_DECIMALS, + asset: assets[position.assetId], + oraclePrice: perpetualMarkets[position.id]?.oraclePrice, + fundingRate: perpetualMarkets[position.id]?.perpetual?.nextFundingRate, + stopLossOrders: allStopLossOrders.filter( + (order: SubaccountOrder) => order.marketId === position.id + ), + takeProfitOrders: allTakeProfitOrders.filter( + (order: SubaccountOrder) => order.marketId === position.id + ), + stepSizeDecimals: + perpetualMarkets[position.id]?.configs?.stepSizeDecimals ?? TOKEN_DECIMALS, + }, + position + ); + }), + [positions, perpetualMarkets, assets, allStopLossOrders, allTakeProfitOrders] + ); + + return ( + <$Table + key={currentMarket ?? 'positions'} + label={stringGetter({ key: STRING_KEYS.POSITIONS })} + data={positionsData} + columns={columnKeys.map((key: PositionsTableColumnKey) => + getPositionsTableColumnDef({ + key, + stringGetter, + width: columnWidths?.[key], + isAccountViewOnly, + showClosePositionAction, + navigateToOrders, + isSinglePosition: positionsData.length === 1, + uiRefresh, + isTablet, + }) + )} + getRowKey={(row: PositionTableRow) => row.id} + onRowAction={ + currentMarket + ? undefined + : (market: Key) => { + navigate(`${AppRoute.Trade}/${market}`, { + state: { from: currentRoute }, + }); + onNavigate?.(); + } + } + getRowAttributes={(row: PositionTableRow) => ({ + 'data-side': row.side.current, + })} + slotEmpty={ + <> + +

{stringGetter({ key: STRING_KEYS.POSITIONS_EMPTY_STATE })}

+ + } + initialPageSize={initialPageSize} + withGradientCardRows={withGradientCardRows} + withOuterBorder={withOuterBorder} + withInnerBorders + withScrollSnapColumns + withScrollSnapRows + withFocusStickyRows + /> + ); + } +); const $Table = styled(Table)` ${tradeViewMixins.horizontalTable}