Test ag-grid optimization approach
This commit is contained in:
parent
8b57f6fdb1
commit
2c28c9dd2d
@ -62,7 +62,7 @@ interface UseMarkets {
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const useMarkets = (): UseMarkets => {
|
||||
export const useMarkets = (updateCallback?: (data: MarketDataSub_marketData) => void): UseMarkets => {
|
||||
const client = useApolloClient();
|
||||
const [markets, setMarkets] = useState<Markets_markets[]>([]);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
@ -81,26 +81,21 @@ export const useMarkets = (): UseMarkets => {
|
||||
|
||||
// Make initial fetch
|
||||
useEffect(() => {
|
||||
const fetchOrders = async () => {
|
||||
(async () => {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const res = await client.query<Markets>({
|
||||
query: MARKETS_QUERY,
|
||||
});
|
||||
|
||||
if (!res.data.markets?.length) return;
|
||||
|
||||
setMarkets(res.data.markets);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchOrders();
|
||||
}, [mergeMarketData, client]);
|
||||
})();
|
||||
}, [client]);
|
||||
|
||||
// Start subscription
|
||||
useEffect(() => {
|
||||
@ -111,6 +106,9 @@ export const useMarkets = (): UseMarkets => {
|
||||
query: MARKET_DATA_SUB,
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
if (updateCallback) {
|
||||
updateCallback(data.marketData);
|
||||
}
|
||||
mergeMarketData(data.marketData);
|
||||
});
|
||||
|
||||
@ -119,7 +117,7 @@ export const useMarkets = (): UseMarkets => {
|
||||
sub.unsubscribe();
|
||||
}
|
||||
};
|
||||
}, [client, mergeMarketData]);
|
||||
}, [client, mergeMarketData, updateCallback]);
|
||||
|
||||
return { markets, error, loading };
|
||||
};
|
||||
|
@ -4,14 +4,68 @@ import {
|
||||
Callout,
|
||||
Intent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { GridApi } from 'ag-grid-community';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useApplyGridTransaction } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export function Index() {
|
||||
const Grid = () => {
|
||||
const rowData = [
|
||||
{ make: 'Toyota', model: 'Celica', price: 35000 },
|
||||
{ make: 'Ford', model: 'Mondeo', price: 32000 },
|
||||
{ make: 'Porsche', model: 'Boxter', price: 72000 },
|
||||
];
|
||||
const ref = useRef(rowData);
|
||||
const getRowNodeId = (data: { make: string }) => data.make;
|
||||
const gridApi = useRef<GridApi | null>(null);
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (!gridApi) return;
|
||||
const update = [];
|
||||
const add = [];
|
||||
|
||||
// split into updates and adds
|
||||
[...rowData].forEach((data) => {
|
||||
if (!gridApi.current) return;
|
||||
|
||||
const rowNode = gridApi.current.getRowNode(getRowNodeId(data));
|
||||
|
||||
if (rowNode) {
|
||||
if (rowNode.data !== data) {
|
||||
update.push(data);
|
||||
}
|
||||
} else {
|
||||
add.push(data);
|
||||
}
|
||||
});
|
||||
// async transaction for optimal handling of high grequency updates
|
||||
if (update.length || add.length) {
|
||||
gridApi.current.applyTransaction({
|
||||
update,
|
||||
add,
|
||||
addIndex: 0,
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
return (
|
||||
<AgGrid
|
||||
onGridReady={(params) => {
|
||||
gridApi.current = params.api;
|
||||
}}
|
||||
getRowNodeId={getRowNodeId}
|
||||
rowData={ref.current}
|
||||
style={{ height: 400, width: 600 }}
|
||||
>
|
||||
<AgGridColumn field="make"></AgGridColumn>
|
||||
<AgGridColumn field="model"></AgGridColumn>
|
||||
<AgGridColumn field="price"></AgGridColumn>
|
||||
</AgGrid>
|
||||
);
|
||||
};
|
||||
|
||||
export function Index() {
|
||||
return (
|
||||
<div className="m-24">
|
||||
<div className="mb-24">
|
||||
@ -29,11 +83,7 @@ export function Index() {
|
||||
</div>
|
||||
</Callout>
|
||||
</div>
|
||||
<AgGrid rowData={rowData} style={{ height: 400, width: 600 }}>
|
||||
<AgGridColumn field="make"></AgGridColumn>
|
||||
<AgGridColumn field="model"></AgGridColumn>
|
||||
<AgGridColumn field="price"></AgGridColumn>
|
||||
</AgGrid>
|
||||
<Grid />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,10 +3,11 @@ import { useRouter } from 'next/router';
|
||||
import { MarketListTable } from '@vegaprotocol/market-list';
|
||||
import { useMarkets } from '../../hooks/use-markets';
|
||||
import { AsyncRenderer } from '../../components/async-renderer';
|
||||
import { updateCallback } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const Markets = () => {
|
||||
const { pathname, push } = useRouter();
|
||||
const { markets, error, loading } = useMarkets();
|
||||
const { markets, error, loading } = useMarkets(updateCallback);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={markets}>
|
||||
@ -22,6 +23,15 @@ const Markets = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const TwoMarkets = () => (<><div style={{height: '50%'}}><Markets /></div><div style={{height: '50%'}}><Markets /></div></>)
|
||||
const TwoMarkets = () => (
|
||||
<>
|
||||
<div style={{ height: '50%' }}>
|
||||
<Markets />
|
||||
</div>
|
||||
<div style={{ height: '50%' }}>
|
||||
<Markets />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
export default TwoMarkets;
|
||||
|
@ -1,89 +1,82 @@
|
||||
import type { GridApi, ValueFormatterParams } from 'ag-grid-community';
|
||||
import {
|
||||
PriceCell,
|
||||
formatNumber,
|
||||
useApplyGridTransaction,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { forwardRef } from 'react';
|
||||
import type { ValueFormatterParams } from 'ag-grid-community';
|
||||
import { PriceCell, formatNumber } from '@vegaprotocol/react-helpers';
|
||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { Markets_markets } from '@vegaprotocol/graphql';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { useRef, useState } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface MarketListTableProps {
|
||||
markets: Markets_markets[];
|
||||
onRowClicked: (marketId: string) => void;
|
||||
}
|
||||
|
||||
export const MarketListTable = ({
|
||||
markets,
|
||||
onRowClicked,
|
||||
}: MarketListTableProps) => {
|
||||
const [initialMarkets] = useState(markets);
|
||||
const gridApi = useRef<GridApi | null>(null);
|
||||
useApplyGridTransaction<Markets_markets>(markets, gridApi.current);
|
||||
export const MarketListTable = forwardRef<AgGridReact, MarketListTableProps>(
|
||||
({ markets, onRowClicked }, ref) => {
|
||||
const [initialMarkets] = useState(markets);
|
||||
const getRowNodeId = (data: Markets_markets) => data.id;
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate="No markets"
|
||||
rowData={initialMarkets}
|
||||
getRowNodeId={(data) => data.id}
|
||||
suppressCellFocus={true}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
}}
|
||||
onGridReady={(params) => {
|
||||
gridApi.current = params.api;
|
||||
}}
|
||||
onRowClicked={({ data }) => onRowClicked(data.id)}
|
||||
components={{ PriceCell }}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName="Market"
|
||||
field="tradableInstrument.instrument.code"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Settlement asset"
|
||||
field="tradableInstrument.instrument.product.settlementAsset.symbol"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="State"
|
||||
field="data"
|
||||
valueFormatter={({ value }: ValueFormatterParams) =>
|
||||
`${value.market.state} (${value.market.tradingMode})`
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Best bid"
|
||||
field="data.bestBidPrice"
|
||||
type="rightAligned"
|
||||
cellRenderer="PriceCell"
|
||||
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
||||
formatNumber(value, data.decimalPlaces)
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Best offer"
|
||||
field="data.bestOfferPrice"
|
||||
type="rightAligned"
|
||||
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
||||
formatNumber(value, data.decimalPlaces)
|
||||
}
|
||||
cellRenderer="PriceCell"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Mark price"
|
||||
field="data.markPrice"
|
||||
type="rightAligned"
|
||||
cellRenderer="PriceCell"
|
||||
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
||||
formatNumber(value, data.decimalPlaces)
|
||||
}
|
||||
/>
|
||||
<AgGridColumn headerName="Description" field="name" />
|
||||
</AgGrid>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate="No markets"
|
||||
rowData={initialMarkets}
|
||||
getRowNodeId={getRowNodeId}
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
}}
|
||||
onRowClicked={({ data }) => onRowClicked(data.id)}
|
||||
components={{ PriceCell }}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName="Market"
|
||||
field="tradableInstrument.instrument.code"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Settlement asset"
|
||||
field="tradableInstrument.instrument.product.settlementAsset.symbol"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="State"
|
||||
field="data"
|
||||
valueFormatter={({ value }: ValueFormatterParams) =>
|
||||
`${value.market.state} (${value.market.tradingMode})`
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Best bid"
|
||||
field="data.bestBidPrice"
|
||||
type="rightAligned"
|
||||
cellRenderer="PriceCell"
|
||||
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
||||
formatNumber(value, data.decimalPlaces)
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Best offer"
|
||||
field="data.bestOfferPrice"
|
||||
type="rightAligned"
|
||||
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
||||
formatNumber(value, data.decimalPlaces)
|
||||
}
|
||||
cellRenderer="PriceCell"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Mark price"
|
||||
field="data.markPrice"
|
||||
type="rightAligned"
|
||||
cellRenderer="PriceCell"
|
||||
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
||||
formatNumber(value, data.decimalPlaces)
|
||||
}
|
||||
/>
|
||||
<AgGridColumn headerName="Description" field="name" />
|
||||
</AgGrid>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default MarketListTable;
|
||||
|
@ -1,10 +1,48 @@
|
||||
import { GridApi } from 'ag-grid-community';
|
||||
import { useEffect } from 'react';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { produce } from 'immer';
|
||||
|
||||
export const useApplyGridTransaction = <T extends { id: string }>(
|
||||
export const updateCallback =
|
||||
<T>(
|
||||
gridApiRef: { current: GridApi | null },
|
||||
getRowNodeId: (row: T) => string
|
||||
) =>
|
||||
(data: T[]) => {
|
||||
if (!gridApiRef.current) return;
|
||||
|
||||
const update: T[] = [];
|
||||
const add: T[] = [];
|
||||
|
||||
// split into updates and adds
|
||||
data.forEach((d) => {
|
||||
if (!gridApiRef.current) return;
|
||||
|
||||
const rowNode = gridApiRef.current.getRowNode(getRowNodeId(d));
|
||||
|
||||
if (rowNode) {
|
||||
if (
|
||||
produce(rowNode.data, (draft: T) => Object.assign(draft, d)) !==
|
||||
rowNode.data
|
||||
) {
|
||||
update.push(d);
|
||||
}
|
||||
} else {
|
||||
add.push(d);
|
||||
}
|
||||
});
|
||||
// async transaction for optimal handling of high grequency updates
|
||||
gridApiRef.current.applyTransactionAsync({
|
||||
update,
|
||||
add,
|
||||
addIndex: 0,
|
||||
});
|
||||
};
|
||||
|
||||
export const useApplyGridTransaction = <T>(
|
||||
data: T[],
|
||||
gridApi: GridApi | null
|
||||
gridApi: GridApi | null,
|
||||
getRowNodeId: (row: T) => string
|
||||
) => {
|
||||
useEffect(() => {
|
||||
if (!gridApi) return;
|
||||
@ -16,7 +54,7 @@ export const useApplyGridTransaction = <T extends { id: string }>(
|
||||
data.forEach((d) => {
|
||||
if (!gridApi) return;
|
||||
|
||||
const rowNode = gridApi.getRowNode(d.id);
|
||||
const rowNode = gridApi.getRowNode(getRowNodeId(d));
|
||||
|
||||
if (rowNode) {
|
||||
if (!isEqual(rowNode.data, d)) {
|
||||
@ -26,11 +64,11 @@ export const useApplyGridTransaction = <T extends { id: string }>(
|
||||
add.push(d);
|
||||
}
|
||||
});
|
||||
|
||||
// async transaction for optimal handling of high grequency updates
|
||||
gridApi.applyTransaction({
|
||||
update,
|
||||
add,
|
||||
addIndex: 0,
|
||||
});
|
||||
}, [data, gridApi]);
|
||||
}, [data, gridApi, getRowNodeId]);
|
||||
};
|
||||
|
@ -15,14 +15,13 @@ const AgGridDarkTheme = dynamic<{ children: React.ReactElement }>(
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export const AgGridThemed = ({
|
||||
style,
|
||||
className,
|
||||
...props
|
||||
}: (AgGridReactProps | AgReactUiProps) & {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
}) => {
|
||||
export const AgGridThemed = React.forwardRef<
|
||||
AgGridReact,
|
||||
(AgGridReactProps | AgReactUiProps) & {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
}
|
||||
>(({ style, className, ...props }, ref) => {
|
||||
const theme = React.useContext(ThemeContext);
|
||||
return (
|
||||
<div
|
||||
@ -33,13 +32,13 @@ export const AgGridThemed = ({
|
||||
>
|
||||
{theme === 'dark' ? (
|
||||
<AgGridDarkTheme>
|
||||
<AgGridReact {...props} />
|
||||
<AgGridReact {...props} ref={ref} />
|
||||
</AgGridDarkTheme>
|
||||
) : (
|
||||
<AgGridLightTheme>
|
||||
<AgGridReact {...props} />
|
||||
<AgGridReact {...props} ref={ref} />
|
||||
</AgGridLightTheme>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,15 +1,28 @@
|
||||
import * as React from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
|
||||
import type {
|
||||
AgGridReactProps,
|
||||
AgReactUiProps,
|
||||
AgGridReact,
|
||||
} from 'ag-grid-react';
|
||||
|
||||
type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
ref?: React.Ref<AgGridReact>;
|
||||
};
|
||||
|
||||
// https://stackoverflow.com/questions/69433673/nextjs-reactdomserver-does-not-yet-support-suspense
|
||||
export const AgGridDynamic = dynamic<
|
||||
(AgGridReactProps | AgReactUiProps) & {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
const AgGridDynamicInternal = dynamic<Props>(
|
||||
() => import('./ag-grid-dynamic-themed').then((mod) => mod.AgGridThemed),
|
||||
{
|
||||
ssr: false,
|
||||
// https://nextjs.org/docs/messages/invalid-dynamic-suspense
|
||||
// suspense: true
|
||||
}
|
||||
>(() => import('./ag-grid-dynamic-themed').then((mod) => mod.AgGridThemed), {
|
||||
ssr: false,
|
||||
// https://nextjs.org/docs/messages/invalid-dynamic-suspense
|
||||
// suspense: true
|
||||
});
|
||||
);
|
||||
|
||||
export const AgGridDynamic = React.forwardRef<AgGridReact>((props, ref) => (
|
||||
<AgGridDynamicInternal {...props} ref={ref} />
|
||||
));
|
||||
|
@ -16,14 +16,13 @@ const AgGridDarkTheme = React.lazy(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
export const AgGridThemed = ({
|
||||
style,
|
||||
className,
|
||||
...props
|
||||
}: (AgGridReactProps | AgReactUiProps) & {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
}) => {
|
||||
export const AgGridThemed = React.forwardRef<
|
||||
AgGridReact,
|
||||
(AgGridReactProps | AgReactUiProps) & {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
}
|
||||
>(({ style, className, ...props }, ref) => {
|
||||
const theme = React.useContext(ThemeContext);
|
||||
return (
|
||||
<div
|
||||
@ -34,13 +33,13 @@ export const AgGridThemed = ({
|
||||
>
|
||||
{theme === 'dark' ? (
|
||||
<AgGridDarkTheme>
|
||||
<AgGridReact {...props} />
|
||||
<AgGridReact {...props} ref={ref} />
|
||||
</AgGridDarkTheme>
|
||||
) : (
|
||||
<AgGridLightTheme>
|
||||
<AgGridReact {...props} />
|
||||
<AgGridReact {...props} ref={ref} />
|
||||
</AgGridLightTheme>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,12 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
|
||||
const LazyAgGridStyled = React.lazy(() =>
|
||||
export const AgGridLazyInternal = React.lazy(() =>
|
||||
import('./ag-grid-lazy-themed').then((module) => ({
|
||||
default: module.AgGridThemed,
|
||||
}))
|
||||
);
|
||||
|
||||
export const AgGridLazy = (
|
||||
props: (AgGridReactProps | AgReactUiProps) & { style: React.CSSProperties }
|
||||
) => <LazyAgGridStyled {...props} />;
|
||||
export const AgGridLazy = React.forwardRef<AgGridReact>((props, ref) => (
|
||||
<AgGridLazyInternal {...props} ref={ref} />
|
||||
));
|
||||
|
Loading…
Reference in New Issue
Block a user