fix(trading): refactor market info accordion to avoid remount on candle reload (#3447)

This commit is contained in:
Bartłomiej Głownia 2023-04-20 11:32:09 +02:00 committed by GitHub
parent 460ccdb3a2
commit c15051d457
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 352 additions and 224 deletions

View File

@ -50,7 +50,7 @@ jobs:
secrets: inherit
lint-test-build:
timeout-minutes: 20
timeout-minutes: 60
needs: node-modules
runs-on: ubuntu-22.04
name: '(CI) lint + unit test + build'

View File

@ -60,9 +60,11 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
<>
<MarketInfoTable
noBorder={false}
data={trigger}
data={{
maxValidPrice: trigger.maxValidPrice,
minValidPrice: trigger.minValidPrice,
}}
decimalPlaces={market.decimalPlaces}
omits={['referencePrice', '__typename']}
/>
<MarketInfoTable
noBorder={false}

View File

@ -108,18 +108,14 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
it('risk model displayed', () => {
cy.getByTestId(marketTitle).contains('Risk model').click();
validateMarketDataRow(0, 'Typename', 'LogNormalRiskModel');
validateMarketDataRow(1, 'Tau', '0.0001140771161');
validateMarketDataRow(2, 'Risk Aversion Parameter', '0.01');
validateMarketDataRow(0, 'Tau', '0.0001140771161');
validateMarketDataRow(1, 'Risk Aversion Parameter', '0.01');
});
it('risk parameters displayed', () => {
cy.getByTestId(marketTitle).contains('Risk parameters').click();
validateMarketDataRow(0, 'Typename', 'LogNormalModelParams');
validateMarketDataRow(1, 'R', '0.016');
validateMarketDataRow(2, 'Sigma', '0.3');
validateMarketDataRow(0, 'R', '0.016');
validateMarketDataRow(1, 'Sigma', '0.3');
});
it('risk factors displayed', () => {

View File

@ -1,5 +1,5 @@
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
import { MarketInfoContainer } from '@vegaprotocol/market-info';
import { MarketInfoAccordionContainer } from '@vegaprotocol/market-info';
import { OrderbookContainer } from '@vegaprotocol/market-depth';
import { OrderListContainer } from '@vegaprotocol/orders';
import { FillsContainer } from '@vegaprotocol/fills';
@ -38,7 +38,7 @@ type MarketDependantView =
| typeof CandlesChartContainer
| typeof DepthChartContainer
| typeof DealTicketContainer
| typeof MarketInfoContainer
| typeof MarketInfoAccordionContainer
| typeof OrderbookContainer
| typeof TradesContainer;
@ -56,7 +56,7 @@ const TradingViews = {
Depth: requiresMarket(DepthChartContainer),
Liquidity: requiresMarket(LiquidityContainer),
Ticket: requiresMarket(DealTicketContainer),
Info: requiresMarket(MarketInfoContainer),
Info: requiresMarket(MarketInfoAccordionContainer),
Orderbook: requiresMarket(OrderbookContainer),
Trades: requiresMarket(TradesContainer),
Positions: PositionsContainer,

View File

@ -13,8 +13,7 @@ import type { OnCellClickHandler } from '../../components/select-market';
import { Header, HeaderStat } from '../../components/header';
import { NO_MARKET } from './constants';
import { MarketMarkPrice } from '../../components/market-mark-price';
import { Last24hPriceChange } from '../../components/last-24h-price-change';
import { Last24hVolume } from '../../components/last-24h-volume';
import { Last24hPriceChange, Last24hVolume } from '@vegaprotocol/market-info';
import { MarketState } from '../../components/market-state';
import { HeaderStatMarketTradingMode } from '../../components/market-trading-mode';
import { MarketLiquiditySupplied } from '../../components/liquidity-supplied';

View File

@ -14,9 +14,8 @@ import type { CandleClose } from '@vegaprotocol/types';
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/market-list';
import { Link } from 'react-router-dom';
import { MarketMarkPrice } from '../market-mark-price';
import { Last24hPriceChange } from '../last-24h-price-change';
import { Last24hPriceChange, Last24hVolume } from '@vegaprotocol/market-info';
import { MarketTradingMode } from '../market-trading-mode';
import { Last24hVolume } from '../last-24h-volume';
import { Links, Routes } from '../../pages/client-router';
const ellipsisClasses = 'whitespace-nowrap overflow-hidden text-ellipsis';

View File

@ -16,19 +16,18 @@ export const useInitialMargin = (
order?: OrderSubmissionBody['orderSubmission']
) => {
const { pubKey } = useVegaWallet();
const commonVariables = { marketId, partyId: pubKey || '' };
const { data: marketData } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId },
});
const { data: activeVolumeAndMargin } = useDataProvider({
dataProvider: volumeAndMarginProvider,
variables: commonVariables,
variables: { marketId, partyId: pubKey || '' },
skip: !pubKey,
});
const { data: marketInfo } = useDataProvider({
dataProvider: marketInfoProvider,
variables: commonVariables,
variables: { marketId },
});
let totalMargin = '0';
let margin = '0';

View File

@ -1,2 +1,4 @@
export * from './market-info';
export * from './last-24h-price-change';
export * from './last-24h-volume';
export * from './fees-breakdown';

View File

@ -9,7 +9,6 @@ import { PriceChangeCell } from '@vegaprotocol/datagrid';
import * as Schema from '@vegaprotocol/types';
import type { CandleClose } from '@vegaprotocol/types';
import { marketCandlesProvider } from '@vegaprotocol/market-list';
import { THROTTLE_UPDATE_TIME } from '../constants';
interface Props {
marketId?: string;
@ -28,8 +27,7 @@ export const Last24hPriceChange = ({
}: Props) => {
const [ref, inView] = useInView({ root: inViewRoot?.current });
const yesterday = useYesterday();
const { data, error } = useThrottledDataProvider(
{
const { data, error } = useThrottledDataProvider({
dataProvider: marketCandlesProvider,
variables: {
marketId: marketId || '',
@ -37,9 +35,7 @@ export const Last24hPriceChange = ({
since: new Date(yesterday).toISOString(),
},
skip: !marketId || !inView,
},
THROTTLE_UPDATE_TIME
);
});
const candles =
data

View File

@ -10,7 +10,6 @@ import {
useYesterday,
} from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import { THROTTLE_UPDATE_TIME } from '../constants';
interface Props {
marketId?: string;
@ -30,8 +29,7 @@ export const Last24hVolume = ({
const yesterday = useYesterday();
const [ref, inView] = useInView({ root: inViewRoot?.current });
const { data } = useThrottledDataProvider(
{
const { data } = useThrottledDataProvider({
dataProvider: marketCandlesProvider,
variables: {
marketId: marketId || '',
@ -39,9 +37,7 @@ export const Last24hVolume = ({
since: new Date(yesterday).toISOString(),
},
skip: !(inView && marketId),
},
THROTTLE_UPDATE_TIME
);
});
const candleVolume = data ? calcCandleVolume(data) : initialValue;
return (
<span ref={ref}>

View File

@ -1,5 +1,5 @@
export * from './info-key-value-table';
export * from './info-market';
export * from './market-info-accordion';
export * from './tooltip-mapping';
export * from './__generated__/MarketInfo';
export * from './market-info-data-provider';

View File

@ -17,7 +17,7 @@ import { tooltipMapping } from './tooltip-mapping';
import type { ReactNode } from 'react';
interface RowProps {
field: string;
value: unknown;
value: ReactNode;
decimalPlaces?: number;
asPercentage?: boolean;
unformatted?: boolean;
@ -36,8 +36,8 @@ const Row = ({
}: RowProps) => {
const className = 'text-black dark:text-white text-sm !px-0';
const getFormattedValue = (value: unknown) => {
if (typeof value !== 'string' && typeof value !== 'number') return null;
const getFormattedValue = (value: ReactNode) => {
if (typeof value !== 'string' && typeof value !== 'number') return value;
if (unformatted || isNaN(Number(value))) {
return value;
}
@ -50,7 +50,7 @@ const Row = ({
return `${formatNumber(Number(value))} ${assetSymbol}`;
};
const formattedValue: string | number | null = getFormattedValue(value);
const formattedValue = getFormattedValue(value);
if (!formattedValue) return null;
return (
@ -70,11 +70,10 @@ const Row = ({
};
export interface MarketInfoTableProps {
data: unknown;
data: Record<string, ReactNode> | null | undefined;
decimalPlaces?: number;
asPercentage?: boolean;
unformatted?: boolean;
omits?: string[];
children?: ReactNode;
assetSymbol?: string;
noBorder?: boolean;
@ -85,7 +84,6 @@ export const MarketInfoTable = ({
decimalPlaces,
asPercentage,
unformatted,
omits = ['__typename'],
children,
assetSymbol,
noBorder,
@ -96,9 +94,7 @@ export const MarketInfoTable = ({
return (
<>
<KeyValueTable>
{Object.entries(data)
.filter(([key]) => !omits.includes(key))
.map(([key, value]) => (
{Object.entries(data).map(([key, value]) => (
<Row
key={key}
field={key}

View File

@ -1,7 +1,7 @@
import { useEnvironment } from '@vegaprotocol/environment';
import { removePaginationWrapper, TokenLinks } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider, useYesterday } from '@vegaprotocol/react-helpers';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import {
Accordion,
@ -11,12 +11,11 @@ import {
Splash,
TinyScroll,
} from '@vegaprotocol/ui-toolkit';
import { useMemo } from 'react';
import { generatePath, Link } from 'react-router-dom';
import { marketInfoWithDataAndCandlesProvider } from './market-info-data-provider';
import { marketInfoProvider } from './market-info-data-provider';
import type { MarketInfoWithDataAndCandles } from './market-info-data-provider';
import type { MarketInfo } from './market-info-data-provider';
import { MarketProposalNotification } from '@vegaprotocol/proposals';
import {
CurrentFeesInfoPanel,
@ -37,8 +36,8 @@ import {
SettlementAssetInfoPanel,
} from './market-info-panels';
export interface InfoProps {
market: MarketInfoWithDataAndCandles;
export interface MarketInfoAccordionProps {
market: MarketInfo;
onSelect?: (id: string, metaKey?: boolean) => void;
}
@ -46,34 +45,21 @@ export interface MarketInfoContainerProps {
marketId: string;
onSelect?: (id: string, metaKey?: boolean) => void;
}
export const MarketInfoContainer = ({
export const MarketInfoAccordionContainer = ({
marketId,
onSelect,
}: MarketInfoContainerProps) => {
const yesterday = useYesterday();
const yTimestamp = useMemo(() => {
return new Date(yesterday).toISOString();
}, [yesterday]);
const variables = useMemo(
() => ({
marketId,
since: yTimestamp,
interval: Schema.Interval.INTERVAL_I1H,
}),
[marketId, yTimestamp]
);
const { data, loading, error, reload } = useDataProvider({
dataProvider: marketInfoWithDataAndCandlesProvider,
dataProvider: marketInfoProvider,
skipUpdates: true,
variables,
variables: { marketId },
});
return (
<AsyncRenderer data={data} loading={loading} error={error} reload={reload}>
{data ? (
<TinyScroll className="h-full overflow-auto">
<Info market={data} onSelect={onSelect} />
<MarketInfoAccordion market={data} onSelect={onSelect} />
</TinyScroll>
) : (
<Splash>
@ -84,7 +70,10 @@ export const MarketInfoContainer = ({
);
};
export const Info = ({ market, onSelect }: InfoProps) => {
const MarketInfoAccordion = ({
market,
onSelect,
}: MarketInfoAccordionProps) => {
const { VEGA_TOKEN_URL } = useEnvironment();
const headerClassName = 'uppercase text-lg';

View File

@ -3,15 +3,8 @@ import type {
MarketInfoQuery,
MarketInfoQueryVariables,
} from './__generated__/MarketInfo';
import {
marketDataProvider,
marketCandlesProvider,
} from '@vegaprotocol/market-list';
import type {
MarketData,
Candle,
MarketCandlesQueryVariables,
} from '@vegaprotocol/market-list';
import { marketDataProvider } from '@vegaprotocol/market-list';
import type { MarketData, Candle } from '@vegaprotocol/market-list';
import { MarketInfoDocument } from './__generated__/MarketInfo';
export type MarketInfo = NonNullable<MarketInfoQuery['market']>;
@ -49,20 +42,3 @@ export const marketInfoWithDataProvider = makeDerivedDataProvider<
}
);
});
export const marketInfoWithDataAndCandlesProvider = makeDerivedDataProvider<
MarketInfoWithDataAndCandles,
never,
MarketCandlesQueryVariables
>([marketInfoProvider, marketDataProvider, marketCandlesProvider], (parts) => {
const market: MarketInfo | null = parts[0];
const marketData: MarketData | null = parts[1];
const candles: Candle[] | null = parts[2];
return (
market && {
...market,
data: marketData || undefined,
candles: candles || undefined,
}
);
});

View File

@ -3,8 +3,8 @@ import { useMemo } from 'react';
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import {
calcCandleVolume,
totalFeesPercentage,
marketDataProvider,
} from '@vegaprotocol/market-list';
import { ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
import {
@ -18,8 +18,8 @@ import { MarketInfoTable } from './info-key-value-table';
import type {
MarketInfo,
MarketInfoWithData,
MarketInfoWithDataAndCandles,
} from './market-info-data-provider';
import { Last24hVolume } from '../last-24h-volume';
import BigNumber from 'bignumber.js';
import type { DataSourceDefinition, SignerKind } from '@vegaprotocol/types';
import { ConditionOperatorMapping } from '@vegaprotocol/types';
@ -27,6 +27,7 @@ import { MarketTradingModeMapping } from '@vegaprotocol/types';
import { useEnvironment } from '@vegaprotocol/environment';
import type { Provider } from '@vegaprotocol/oracles';
import { useOracleProofs } from '@vegaprotocol/oracles';
import { useDataProvider } from '@vegaprotocol/react-helpers';
type PanelProps = Pick<
ComponentProps<typeof MarketInfoTable>,
@ -37,14 +38,6 @@ type MarketInfoProps = {
market: MarketInfo;
};
type MarketInfoWithDataProps = {
market: MarketInfoWithData;
};
type MarketInfoWithDataAndCandlesProps = {
market: MarketInfoWithDataAndCandles;
};
export const CurrentFeesInfoPanel = ({
market,
...props
@ -52,7 +45,9 @@ export const CurrentFeesInfoPanel = ({
<>
<MarketInfoTable
data={{
...market.fees.factors,
makerFee: market.fees.factors.makerFee,
infrastructureFee: market.fees.factors.infrastructureFee,
liquidityFee: market.fees.factors.liquidityFee,
totalFees: totalFeesPercentage(market.fees.factors),
}}
asPercentage={true}
@ -69,18 +64,22 @@ export const CurrentFeesInfoPanel = ({
export const MarketPriceInfoPanel = ({
market,
...props
}: MarketInfoWithDataProps & PanelProps) => {
}: MarketInfoProps & PanelProps) => {
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || '';
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
return (
<>
<MarketInfoTable
data={{
markPrice: market.data?.markPrice,
bestBidPrice: market.data?.bestBidPrice,
bestOfferPrice: market.data?.bestOfferPrice,
markPrice: data?.markPrice,
bestBidPrice: data?.bestBidPrice,
bestOfferPrice: data?.bestOfferPrice,
quoteUnit: market.tradableInstrument.instrument.product.quoteName,
}}
decimalPlaces={market.decimalPlaces}
@ -99,8 +98,11 @@ export const MarketPriceInfoPanel = ({
export const MarketVolumeInfoPanel = ({
market,
...props
}: MarketInfoWithDataAndCandlesProps & PanelProps) => {
const last24hourVolume = market.candles && calcCandleVolume(market.candles);
}: MarketInfoProps & PanelProps) => {
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
const dash = (value: string | undefined) =>
value && value !== '0' ? value : '-';
@ -108,12 +110,17 @@ export const MarketVolumeInfoPanel = ({
return (
<MarketInfoTable
data={{
'24hourVolume': dash(last24hourVolume),
openInterest: dash(market.data?.openInterest),
bestBidVolume: dash(market.data?.bestBidVolume),
bestOfferVolume: dash(market.data?.bestOfferVolume),
bestStaticBidVolume: dash(market.data?.bestStaticBidVolume),
bestStaticOfferVolume: dash(market.data?.bestStaticOfferVolume),
'24hourVolume': (
<Last24hVolume
marketId={market.id}
positionDecimalPlaces={market.positionDecimalPlaces}
/>
),
openInterest: dash(data?.openInterest),
bestBidVolume: dash(data?.bestBidVolume),
bestOfferVolume: dash(data?.bestOfferVolume),
bestStaticBidVolume: dash(data?.bestStaticBidVolume),
bestStaticOfferVolume: dash(data?.bestStaticOfferVolume),
}}
decimalPlaces={market.positionDecimalPlaces}
{...props}
@ -176,7 +183,7 @@ export const InstrumentInfoPanel = ({
marketName: market.tradableInstrument.instrument.name,
code: market.tradableInstrument.instrument.code,
productType: market.tradableInstrument.instrument.product.__typename,
...market.tradableInstrument.instrument.product,
quoteName: market.tradableInstrument.instrument.product.quoteName,
}}
{...props}
/>
@ -239,38 +246,52 @@ export const MetadataInfoPanel = ({
export const RiskModelInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
}: MarketInfoProps & PanelProps) => {
if (market.tradableInstrument.riskModel.__typename !== 'LogNormalRiskModel') {
return null;
}
const { tau, riskAversionParameter } = market.tradableInstrument.riskModel;
return (
<MarketInfoTable
data={market.tradableInstrument.riskModel}
unformatted={true}
omits={[]}
data={{ tau, riskAversionParameter }}
unformatted
{...props}
/>
);
};
export const RiskParametersInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
}: MarketInfoProps & PanelProps) => {
if (market.tradableInstrument.riskModel.__typename === 'LogNormalRiskModel') {
const { r, sigma, mu } = market.tradableInstrument.riskModel.params;
return <MarketInfoTable data={{ r, sigma, mu }} unformatted {...props} />;
}
if (market.tradableInstrument.riskModel.__typename === 'SimpleRiskModel') {
const { factorLong, factorShort } =
market.tradableInstrument.riskModel.params;
return (
<MarketInfoTable
data={market.tradableInstrument.riskModel.params}
unformatted={true}
omits={[]}
data={{ factorLong, factorShort }}
unformatted
{...props}
/>
);
}
return null;
};
export const RiskFactorsInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
<MarketInfoTable
data={market.riskFactors}
unformatted={true}
omits={['market', '__typename']}
{...props}
/>
);
}: MarketInfoProps & PanelProps) => {
if (!market.riskFactors) {
return null;
}
const { short, long } = market.riskFactors;
return <MarketInfoTable data={{ short, long }} unformatted {...props} />;
};
export const PriceMonitoringBoundsInfoPanel = ({
market,
@ -278,13 +299,17 @@ export const PriceMonitoringBoundsInfoPanel = ({
...props
}: {
triggerIndex: number;
} & MarketInfoWithDataProps &
} & MarketInfoProps &
PanelProps) => {
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || '';
const trigger =
market.priceMonitoringSettings?.parameters?.triggers?.[triggerIndex];
const bounds = market.data?.priceMonitoringBounds?.[triggerIndex];
const bounds = data?.priceMonitoringBounds?.[triggerIndex];
if (!trigger) {
console.error(
`Could not find data for trigger ${triggerIndex} (market id: ${market.id})`
@ -334,7 +359,11 @@ export const LiquidityMonitoringParametersInfoPanel = ({
<MarketInfoTable
data={{
triggeringRatio: market.liquidityMonitoringParameters.triggeringRatio,
...market.liquidityMonitoringParameters.targetStakeParameters,
timeWindow:
market.liquidityMonitoringParameters.targetStakeParameters.timeWindow,
scalingFactor:
market.liquidityMonitoringParameters.targetStakeParameters
.scalingFactor,
}}
{...props}
/>
@ -343,17 +372,21 @@ export const LiquidityMonitoringParametersInfoPanel = ({
export const LiquidityInfoPanel = ({
market,
...props
}: MarketInfoWithDataProps & PanelProps) => {
}: MarketInfoProps & PanelProps) => {
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
return (
<MarketInfoTable
data={{
targetStake: market.data && market.data.targetStake,
suppliedStake: market.data && market.data?.suppliedStake,
marketValueProxy: market.data && market.data.marketValueProxy,
targetStake: data?.targetStake,
suppliedStake: data?.suppliedStake,
marketValueProxy: data?.marketValueProxy,
}}
decimalPlaces={assetDecimals}
assetSymbol={assetSymbol}
@ -365,12 +398,16 @@ export const LiquidityInfoPanel = ({
export const LiquidityPriceRangeInfoPanel = ({
market,
...props
}: MarketInfoWithDataProps & PanelProps) => {
}: MarketInfoProps & PanelProps) => {
const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || '';
const liquidityPriceRange = formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100)
);
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
return (
<>
<p className="text-xs mb-4">
@ -386,20 +423,20 @@ export const LiquidityPriceRangeInfoPanel = ({
data={{
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
lowestPrice:
market.data?.midPrice &&
data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.minus(market.lpPriceRange)
.times(market.data.midPrice)
.times(data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
highestPrice:
market.data?.midPrice &&
data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.plus(market.lpPriceRange)
.times(market.data.midPrice)
.times(data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
@ -418,7 +455,15 @@ export const OracleInfoPanel = ({
const { VEGA_EXPLORER_URL, ORACLE_PROOFS_URL } = useEnvironment();
const { data } = useOracleProofs(ORACLE_PROOFS_URL);
return (
<MarketInfoTable data={product.dataSourceSpecBinding} {...props}>
<MarketInfoTable
data={{
settlementDataProperty:
product.dataSourceSpecBinding.settlementDataProperty,
tradingTerminationProperty:
product.dataSourceSpecBinding.tradingTerminationProperty,
}}
{...props}
>
<div
className="flex flex-col gap-2 mt-4"
data-testid="oracle-proof-links"

View File

@ -354,10 +354,10 @@ export const volumeAndMarginProvider = makeDerivedDataProvider<
partyId,
marketId,
}),
(callback, client, variables) =>
marketDataProvider(callback, client, { marketId: variables.marketId }),
(callback, client, variables) =>
marketInfoProvider(callback, client, { marketId: variables.marketId }),
(callback, client, { marketId }) =>
marketDataProvider(callback, client, { marketId }),
(callback, client, { marketId }) =>
marketInfoProvider(callback, client, { marketId }),
openVolumeDataProvider,
],
(data) => {

View File

@ -6,7 +6,7 @@ import { MockedProvider } from '@apollo/client/testing';
type Data = number;
type Delta = number;
type Variables = { partyId: string };
type Variables = { partyId: string; marketIds?: string[] };
const unsubscribe = jest.fn();
const reload = jest.fn();
@ -156,6 +156,121 @@ describe('useDataProvider hook', () => {
expect(insert.mock.calls[1][0].insertionData).toEqual(insertionData);
});
it('calls dataProvider with updated variables if skip switched to true', async () => {
const { rerender } = render({
dataProvider,
variables: { partyId: '' },
skip: true,
});
expect(dataProvider).toBeCalledTimes(0);
rerender({
dataProvider,
variables,
});
expect(dataProvider).toBeCalledTimes(1);
expect(dataProvider.mock.calls[0][2]).toEqual(variables);
});
it('uses same data provider when rerendered with equal variables', async () => {
const { rerender } = render({
dataProvider,
variables: { ...variables, marketIds: ['a', 'b'] },
});
expect(dataProvider).toBeCalledTimes(1);
rerender({
dataProvider,
variables: { ...variables, marketIds: ['b', 'a'] },
});
expect(dataProvider).toBeCalledTimes(1);
rerender({
dataProvider,
variables: { ...variables },
});
expect(dataProvider).toBeCalledTimes(2);
});
it('calls new update and insert when replaced', async () => {
const { rerender } = render({
dataProvider,
update,
insert,
variables,
});
const data = 0;
const delta = 0;
const insertionData = 0;
const callback = dataProvider.mock.calls[0][0];
await act(async () => {
callback({ ...updateCallbackPayload, data });
});
const newUpdate = jest.fn();
const newInsert = jest.fn();
expect(update).toBeCalledTimes(2);
expect(insert).toBeCalledTimes(0);
rerender({ dataProvider, update: newUpdate, insert: newInsert, variables });
await act(async () => {
callback({
...updateCallbackPayload,
data: data,
delta,
isUpdate: true,
});
});
expect(newUpdate).toBeCalledTimes(1);
await act(async () => {
callback({
...updateCallbackPayload,
data,
insertionData,
isInsert: true,
});
});
expect(newUpdate).toBeCalledTimes(2);
expect(newInsert).toBeCalledTimes(1);
expect(update).toBeCalledTimes(2);
expect(insert).toBeCalledTimes(0);
});
it('skip updates if skipUpdates is true', async () => {
const { result, rerender } = render({
dataProvider,
update,
variables,
skipUpdates: true,
});
expect(update).toBeCalledTimes(1);
let data = 0;
const delta = 1;
const callback = dataProvider.mock.calls[0][0];
await act(async () => {
callback({ ...updateCallbackPayload, data });
});
expect(update).toBeCalledTimes(2);
expect(result.current.data).toEqual(data);
await act(async () => {
callback({
...updateCallbackPayload,
data: data + delta,
delta,
isUpdate: true,
});
});
expect(update).toBeCalledTimes(2);
expect(result.current.data).toEqual(data);
rerender({ dataProvider, variables, update });
expect(update).toBeCalledTimes(2);
await act(async () => {
callback({
...updateCallbackPayload,
data: (data = data + delta),
delta,
isUpdate: true,
});
});
expect(update).toBeCalledTimes(3);
expect(result.current.data).toEqual(data);
expect(dataProvider).toBeCalledTimes(1);
});
it('change data provider instance on variables change', async () => {
const { result, rerender } = render({ dataProvider, update, variables });
const callback = dataProvider.mock.calls[0][0];

View File

@ -63,6 +63,9 @@ export const useDataProvider = <
const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined);
const loadRef = useRef<Load<Data> | undefined>(undefined);
const variablesRef = useRef<Variables>(props.variables);
const updateRef = useRef(update);
const insertRef = useRef(insert);
const skipUpdatesRef = useRef(skipUpdates);
const variables = useMemo(() => {
if (
!isEqualWith(
@ -91,8 +94,7 @@ export const useDataProvider = <
}
return Promise.reject();
}, []);
const callback = useCallback<UpdateCallback<Data, Delta>>(
(args) => {
const callback = useCallback<UpdateCallback<Data, Delta>>((args) => {
const {
data,
delta,
@ -110,35 +112,51 @@ export const useDataProvider = <
if (!loading) {
if (
isUpdate &&
!skipUpdates &&
update &&
update({ delta, data, totalCount })
(skipUpdatesRef.current ||
(!skipUpdatesRef.current &&
updateRef.current &&
updateRef.current({ delta, data, totalCount })))
) {
return;
}
if (isInsert && insert && insert({ insertionData, data, totalCount })) {
if (
isInsert &&
insertRef.current &&
insertRef.current({ insertionData, data, totalCount })
) {
return;
}
}
setTotalCount(totalCount);
setData(data);
if (!loading && !isUpdate && update) {
update({ data });
if (!loading && !isUpdate && updateRef.current) {
updateRef.current({ data });
}
},
[update, insert, skipUpdates]
);
}, []);
useEffect(() => {
updateRef.current = update;
}, [update]);
useEffect(() => {
insertRef.current = insert;
}, [insert]);
useEffect(() => {
skipUpdatesRef.current = skipUpdates;
}, [skipUpdates]);
useEffect(() => {
setData(null);
setError(undefined);
setTotalCount(undefined);
if (update) {
update({ data: null });
if (updateRef.current) {
updateRef.current({ data: null });
}
if (skip) {
setLoading(false);
if (update) {
update({ data: null });
if (updateRef.current) {
updateRef.current({ data: null });
}
return;
}
@ -157,7 +175,7 @@ export const useDataProvider = <
loadRef.current = undefined;
return unsubscribe();
};
}, [client, dataProvider, callback, variables, skip, update]);
}, [client, dataProvider, callback, variables, skip]);
return {
data,
loading,
@ -175,7 +193,7 @@ export const useThrottledDataProvider = <
Variables extends OperationVariables = OperationVariables
>(
params: Omit<useDataProviderParams<Data, Delta, Variables>, 'update'>,
wait?: number
wait = 500
) => {
const [data, setData] = useState<Data | null>(null);
const dataRef = useRef<Data | null>(null);