Use data markets data provider instead of use-markets hook
This commit is contained in:
parent
2c28c9dd2d
commit
4698e532c1
166
apps/trading/data-providers/markets-data-provider.ts
Normal file
166
apps/trading/data-providers/markets-data-provider.ts
Normal file
@ -0,0 +1,166 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { produce } from 'immer';
|
||||
import type { ApolloClient } from '@apollo/client';
|
||||
import type { Subscription } from 'zen-observable-ts';
|
||||
import {
|
||||
Markets,
|
||||
Markets_markets,
|
||||
MarketDataSub,
|
||||
MarketDataSub_marketData,
|
||||
} from '@vegaprotocol/graphql';
|
||||
|
||||
const MARKET_DATA_FRAGMENT = gql`
|
||||
fragment MarketDataFields on MarketData {
|
||||
market {
|
||||
id
|
||||
state
|
||||
tradingMode
|
||||
}
|
||||
bestBidPrice
|
||||
bestOfferPrice
|
||||
markPrice
|
||||
}
|
||||
`;
|
||||
|
||||
const MARKETS_QUERY = gql`
|
||||
${MARKET_DATA_FRAGMENT}
|
||||
query Markets {
|
||||
markets {
|
||||
id
|
||||
name
|
||||
decimalPlaces
|
||||
data {
|
||||
...MarketDataFields
|
||||
}
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
code
|
||||
product {
|
||||
... on Future {
|
||||
settlementAsset {
|
||||
symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const MARKET_DATA_SUB = gql`
|
||||
${MARKET_DATA_FRAGMENT}
|
||||
subscription MarketDataSub {
|
||||
marketData {
|
||||
...MarketDataFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface CallbackArg {
|
||||
data?: Markets_markets[];
|
||||
error?: Error;
|
||||
loading: boolean;
|
||||
delta?: MarketDataSub_marketData;
|
||||
}
|
||||
|
||||
export interface Callback {
|
||||
(arg: CallbackArg): void;
|
||||
}
|
||||
|
||||
const callbacks: Callback[] = [];
|
||||
const updateQueue: MarketDataSub_marketData[] = [];
|
||||
|
||||
let data: Markets_markets[] = undefined;
|
||||
let error: Error = undefined;
|
||||
let loading = false;
|
||||
let client: ApolloClient<object> = undefined;
|
||||
let subscription: Subscription = undefined;
|
||||
|
||||
const notify = (callback, delta?: MarketDataSub_marketData) => {
|
||||
callback({
|
||||
data,
|
||||
error,
|
||||
loading,
|
||||
delta,
|
||||
});
|
||||
};
|
||||
|
||||
const notifyAll = (delta?: MarketDataSub_marketData) => {
|
||||
callbacks.forEach((callback) => notify(callback, delta));
|
||||
};
|
||||
|
||||
const update = (draft: Markets_markets[], delta: MarketDataSub_marketData) => {
|
||||
const index = draft.findIndex((m) => m.id === delta.market.id);
|
||||
if (index !== -1) {
|
||||
draft[index].data = delta;
|
||||
}
|
||||
// @TODO - else push new market to draft
|
||||
};
|
||||
|
||||
const initialize = async () => {
|
||||
if (subscription) {
|
||||
return;
|
||||
}
|
||||
loading = true;
|
||||
error = null;
|
||||
notifyAll();
|
||||
subscription = client
|
||||
.subscribe<MarketDataSub>({
|
||||
query: MARKET_DATA_SUB,
|
||||
})
|
||||
.subscribe(({ data: delta }) => {
|
||||
if (loading) {
|
||||
updateQueue.push(delta.marketData);
|
||||
} else {
|
||||
data = produce(data, (draft) => {
|
||||
update(draft, delta.marketData);
|
||||
});
|
||||
notifyAll(delta.marketData);
|
||||
}
|
||||
});
|
||||
try {
|
||||
const res = await client.query<Markets>({
|
||||
query: MARKETS_QUERY,
|
||||
});
|
||||
data = res.data.markets;
|
||||
if (updateQueue) {
|
||||
data = produce(data, (draft) => {
|
||||
while (updateQueue.length) {
|
||||
update(draft, updateQueue.shift());
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
error = e;
|
||||
subscription.unsubscribe();
|
||||
subscription = undefined;
|
||||
} finally {
|
||||
loading = false;
|
||||
notifyAll();
|
||||
}
|
||||
};
|
||||
|
||||
const unsubscribe = (callback: Callback) => {
|
||||
callbacks.splice(callbacks.indexOf(callback), 1);
|
||||
if (callbacks.length === 0) {
|
||||
subscription.unsubscribe();
|
||||
subscription = undefined;
|
||||
data = undefined;
|
||||
error = undefined;
|
||||
loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
export const subscribe = (c: ApolloClient<object>, callback) => {
|
||||
if (!client) {
|
||||
client = c;
|
||||
}
|
||||
callbacks.push(callback);
|
||||
if (callbacks.length === 1) {
|
||||
initialize();
|
||||
} else {
|
||||
notify(callback);
|
||||
}
|
||||
return () => unsubscribe(callback);
|
||||
};
|
@ -81,21 +81,26 @@ export const useMarkets = (updateCallback?: (data: MarketDataSub_marketData) =>
|
||||
|
||||
// Make initial fetch
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchOrders = 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);
|
||||
}
|
||||
})();
|
||||
}, [client]);
|
||||
};
|
||||
|
||||
fetchOrders();
|
||||
}, [mergeMarketData, client]);
|
||||
|
||||
// Start subscription
|
||||
useEffect(() => {
|
||||
@ -106,9 +111,6 @@ export const useMarkets = (updateCallback?: (data: MarketDataSub_marketData) =>
|
||||
query: MARKET_DATA_SUB,
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
if (updateCallback) {
|
||||
updateCallback(data.marketData);
|
||||
}
|
||||
mergeMarketData(data.marketData);
|
||||
});
|
||||
|
||||
@ -117,7 +119,7 @@ export const useMarkets = (updateCallback?: (data: MarketDataSub_marketData) =>
|
||||
sub.unsubscribe();
|
||||
}
|
||||
};
|
||||
}, [client, mergeMarketData, updateCallback]);
|
||||
}, [client, mergeMarketData]);
|
||||
|
||||
return { markets, error, loading };
|
||||
};
|
||||
|
@ -1,18 +1,75 @@
|
||||
import { Markets } from '@vegaprotocol/graphql';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { produce } from 'immer';
|
||||
import assign from 'assign-deep';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
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';
|
||||
import { MarketListTable, getRowNodeId } from '@vegaprotocol/market-list';
|
||||
import {
|
||||
Markets_markets,
|
||||
Markets_markets_data
|
||||
} from '@vegaprotocol/graphql';
|
||||
|
||||
import { subscribe } from '../../data-providers/markets-data-provider';
|
||||
import type { CallbackArg } from '../../data-providers/markets-data-provider';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
|
||||
const Markets = () => {
|
||||
const { pathname, push } = useRouter();
|
||||
const { markets, error, loading } = useMarkets(updateCallback);
|
||||
const [markets, setMarkets] = useState<Markets_markets[]>(undefined);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<Error>(undefined);
|
||||
const client = useApolloClient();
|
||||
const gridRef = useRef<AgGridReact>();
|
||||
const initialized = useRef<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
return subscribe(client, ({ data, error, loading, delta }: CallbackArg) => {
|
||||
setError(error);
|
||||
setLoading(loading);
|
||||
if (!error && !loading) {
|
||||
if (!initialized.current || !gridRef.current) {
|
||||
initialized.current = true;
|
||||
setMarkets(data);
|
||||
} else {
|
||||
const update: Markets_markets[] = [];
|
||||
const add: Markets_markets[] = [];
|
||||
|
||||
// split into updates and adds
|
||||
if (!gridRef.current) return;
|
||||
const rowNode = gridRef.current.api.getRowNode(
|
||||
getRowNodeId(delta.market)
|
||||
);
|
||||
|
||||
if (rowNode) {
|
||||
const updatedData = produce(
|
||||
rowNode.data.data,
|
||||
(draft: Markets_markets_data) => assign(draft, delta)
|
||||
);
|
||||
if (updatedData !== rowNode.data.data) {
|
||||
update.push({ ...rowNode.data, data: delta });
|
||||
}
|
||||
} /* else {
|
||||
add.push(d);
|
||||
}*/
|
||||
// async transaction for optimal handling of high grequency updates
|
||||
if (update.length || add.length) {
|
||||
gridRef.current.api.applyTransactionAsync({
|
||||
update,
|
||||
add,
|
||||
addIndex: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [client, initialized]);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={markets}>
|
||||
{(data) => (
|
||||
<MarketListTable
|
||||
ref={gridRef}
|
||||
markets={data}
|
||||
onRowClicked={(id) =>
|
||||
push(`${pathname}/${id}?portfolio=orders&trade=orderbook`)
|
||||
@ -23,15 +80,8 @@ const Markets = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const TwoMarkets = () => (
|
||||
<>
|
||||
<div style={{ height: '50%' }}>
|
||||
<Markets />
|
||||
</div>
|
||||
<div style={{ height: '50%' }}>
|
||||
<Markets />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
export default Markets;
|
||||
|
||||
export default TwoMarkets;
|
||||
// const TwoMarkets = () => (<><div style={{height: '50%'}}><Markets /></div><div style={{height: '50%'}}><Markets /></div></>)
|
||||
|
||||
// export default TwoMarkets;
|
||||
|
@ -12,23 +12,24 @@ interface MarketListTableProps {
|
||||
onRowClicked: (marketId: string) => void;
|
||||
}
|
||||
|
||||
export const getRowNodeId = (data: { id: string }) => data.id;
|
||||
|
||||
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}
|
||||
rowData={markets}
|
||||
getRowNodeId={getRowNodeId}
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
}}
|
||||
onRowClicked={({ data }) => onRowClicked(data.id)}
|
||||
onRowClicked={({ data }: { data: Markets_markets }) =>
|
||||
onRowClicked(data.id)
|
||||
}
|
||||
components={{ PriceCell }}
|
||||
>
|
||||
<AgGridColumn
|
||||
|
@ -15,13 +15,16 @@ const AgGridDarkTheme = dynamic<{ children: React.ReactElement }>(
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export const AgGridThemed = React.forwardRef<
|
||||
AgGridReact,
|
||||
(AgGridReactProps | AgReactUiProps) & {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
}
|
||||
>(({ style, className, ...props }, ref) => {
|
||||
export const AgGridThemed = ({
|
||||
style,
|
||||
className,
|
||||
gridRef,
|
||||
...props
|
||||
}: (AgGridReactProps | AgReactUiProps) & {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
gridRef?: React.ForwardedRef<AgGridReact>;
|
||||
}) => {
|
||||
const theme = React.useContext(ThemeContext);
|
||||
return (
|
||||
<div
|
||||
@ -32,13 +35,13 @@ export const AgGridThemed = React.forwardRef<
|
||||
>
|
||||
{theme === 'dark' ? (
|
||||
<AgGridDarkTheme>
|
||||
<AgGridReact {...props} ref={ref} />
|
||||
<AgGridReact {...props} ref={gridRef} />
|
||||
</AgGridDarkTheme>
|
||||
) : (
|
||||
<AgGridLightTheme>
|
||||
<AgGridReact {...props} ref={ref} />
|
||||
<AgGridReact {...props} ref={gridRef} />
|
||||
</AgGridLightTheme>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
@ -10,7 +10,7 @@ import type {
|
||||
type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
ref?: React.Ref<AgGridReact>;
|
||||
gridRef?: React.Ref<AgGridReact>;
|
||||
};
|
||||
|
||||
// https://stackoverflow.com/questions/69433673/nextjs-reactdomserver-does-not-yet-support-suspense
|
||||
@ -23,6 +23,6 @@ const AgGridDynamicInternal = dynamic<Props>(
|
||||
}
|
||||
);
|
||||
|
||||
export const AgGridDynamic = React.forwardRef<AgGridReact>((props, ref) => (
|
||||
<AgGridDynamicInternal {...props} ref={ref} />
|
||||
));
|
||||
export const AgGridDynamic = React.forwardRef<AgGridReact, Props>(
|
||||
(props, ref) => <AgGridDynamicInternal {...props} gridRef={ref} />
|
||||
);
|
||||
|
@ -27,6 +27,7 @@
|
||||
"ag-grid-community": "^27.0.1",
|
||||
"ag-grid-react": "^27.0.1",
|
||||
"apollo": "^2.33.9",
|
||||
"assign-deep": "^1.0.1",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"bignumber.js": "^9.0.2",
|
||||
"classnames": "^2.3.1",
|
||||
|
12
yarn.lock
12
yarn.lock
@ -6727,11 +6727,23 @@ assertion-error@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
|
||||
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
|
||||
|
||||
assign-deep@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/assign-deep/-/assign-deep-1.0.1.tgz#b6d21d74e2f28bf6592e4c0c541bed6ab59c5f27"
|
||||
integrity sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==
|
||||
dependencies:
|
||||
assign-symbols "^2.0.2"
|
||||
|
||||
assign-symbols@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
||||
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
|
||||
|
||||
assign-symbols@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-2.0.2.tgz#0fb9191dd9d617042746ecfc354f3a3d768a0c98"
|
||||
integrity sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==
|
||||
|
||||
ast-types-flow@^0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
|
||||
|
Loading…
Reference in New Issue
Block a user