Skip to content

Commit 69c6883

Browse files
committed
memoize selectors for lifecycle events
1 parent 4247be3 commit 69c6883

File tree

5 files changed

+112
-127
lines changed

5 files changed

+112
-127
lines changed

src/bonsai/ontology.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { IndexerWsTradesUpdateObject } from '@/types/indexer/indexerManual';
1212
import { type RootState } from '@/state/_store';
1313
import { getCurrentMarketId } from '@/state/currentMarketSelectors';
1414

15-
import { SpotTokenMetadataResponse } from '@/clients/spotApi';
15+
import { TokenInfo } from '@/clients/spotApi';
1616
import { RecordValueType } from '@/lib/typeUtils';
1717

1818
import { HistoricalFundingObject } from './calculators/funding';
@@ -220,7 +220,7 @@ interface BonsaiCoreShape {
220220
loading: BasicSelector<LoadableStatus>;
221221
};
222222
tokenMetadata: {
223-
data: BasicSelector<SpotTokenMetadataResponse | undefined>;
223+
data: BasicSelector<TokenInfo | undefined>;
224224
loading: BasicSelector<LoadableStatus>;
225225
};
226226
};

src/bonsai/rest/spot.ts

Lines changed: 75 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -6,117 +6,100 @@ import { SOL_MINT_ADDRESS } from '@/constants/tokens';
66
import { type RootStore } from '@/state/_store';
77
import { appQueryClient } from '@/state/appQueryClient';
88
import { getCurrentPath, getSpotApiEndpoint } from '@/state/appSelectors';
9+
import { createAppSelector } from '@/state/appTypes';
910
import { setSolPrice, setTokenMetadata } from '@/state/raw';
1011
import { getCurrentSpotToken } from '@/state/spot';
1112

1213
import { SpotApiClient } from '@/clients/spotApi';
1314

1415
import { createStoreEffect } from '../lib/createStoreEffect';
1516
import { loadableIdle } from '../lib/loadable';
16-
import { mapLoadableData } from '../lib/mapLoadable';
1717
import { logBonsaiError, wrapAndLogBonsaiError } from '../logs';
1818
import { queryResultToLoadable } from './lib/queryResultToLoadable';
1919
import { safeSubscribeObserver } from './lib/safeSubscribe';
2020

21-
export function setUpTokenMetadataQuery(store: RootStore) {
22-
return createStoreEffect(
23-
store,
24-
(state) => {
25-
const currentSpotToken = getCurrentSpotToken(state);
26-
const spotApiEndpoint = getSpotApiEndpoint(state);
21+
const getSpotApiEndpointWhenOnSpotPage = createAppSelector(
22+
[getCurrentPath, getSpotApiEndpoint, getCurrentSpotToken],
23+
(currentPath, spotApiEndpoint, currentSpotToken) => {
24+
if (!currentPath.startsWith('/spot') || !currentSpotToken) {
25+
return null;
26+
}
27+
return { endpoint: spotApiEndpoint, currentSpotToken };
28+
}
29+
);
2730

28-
// Only poll when there's an active spot token
29-
if (!currentSpotToken) {
30-
return null;
31-
}
31+
export function setUpTokenMetadataQuery(store: RootStore) {
32+
return createStoreEffect(store, getSpotApiEndpointWhenOnSpotPage, (params) => {
33+
if (!params) {
34+
store.dispatch(setTokenMetadata(loadableIdle()));
35+
return () => {};
36+
}
3237

33-
return { endpoint: spotApiEndpoint, mint: currentSpotToken };
34-
},
35-
(params) => {
36-
if (!params) {
37-
store.dispatch(setTokenMetadata(loadableIdle()));
38-
return () => {};
38+
const { endpoint, currentSpotToken } = params;
39+
const spotApiClient = new SpotApiClient(endpoint);
40+
41+
const observer = new QueryObserver(appQueryClient, {
42+
queryKey: ['spot', 'tokenMetadata', currentSpotToken],
43+
queryFn: wrapAndLogBonsaiError(
44+
() => spotApiClient.getTokenMetadata(currentSpotToken),
45+
'tokenMetadata'
46+
),
47+
retry: 5,
48+
retryDelay: (attempt) => timeUnits.second * 3 * 2 ** attempt,
49+
});
50+
51+
const unsubscribe = safeSubscribeObserver(observer, (result) => {
52+
try {
53+
store.dispatch(setTokenMetadata(queryResultToLoadable(result)));
54+
} catch (e) {
55+
logBonsaiError(
56+
'setUpTokenMetadataQuery',
57+
'Error handling result from react query',
58+
e,
59+
result
60+
);
3961
}
62+
});
4063

41-
const { endpoint, mint } = params;
42-
const spotApiClient = new SpotApiClient(endpoint);
43-
44-
const observer = new QueryObserver(appQueryClient, {
45-
queryKey: ['spot', 'tokenMetadata', mint],
46-
queryFn: wrapAndLogBonsaiError(() => spotApiClient.getTokenMetadata(mint), 'tokenMetadata'),
47-
retry: 5,
48-
retryDelay: (attempt) => timeUnits.second * 3 * 2 ** attempt,
49-
});
50-
51-
const unsubscribe = safeSubscribeObserver(observer, (result) => {
52-
try {
53-
store.dispatch(setTokenMetadata(queryResultToLoadable(result)));
54-
} catch (e) {
55-
logBonsaiError(
56-
'setUpTokenMetadataQuery',
57-
'Error handling result from react query',
58-
e,
59-
result
60-
);
61-
}
62-
});
63-
64-
return () => {
65-
store.dispatch(setTokenMetadata(loadableIdle()));
66-
unsubscribe();
67-
};
68-
}
69-
);
64+
return () => {
65+
store.dispatch(setTokenMetadata(loadableIdle()));
66+
unsubscribe();
67+
};
68+
});
7069
}
7170

7271
export function setUpSolPriceQuery(store: RootStore) {
73-
return createStoreEffect(
74-
store,
75-
(state) => {
76-
const currentPath = getCurrentPath(state);
77-
const spotApiEndpoint = getSpotApiEndpoint(state);
78-
79-
// Only poll when user is on the spot page
80-
if (!currentPath.startsWith('/spot')) {
81-
return null;
82-
}
72+
return createStoreEffect(store, getSpotApiEndpointWhenOnSpotPage, (params) => {
73+
if (!params) {
74+
store.dispatch(setSolPrice(loadableIdle()));
75+
return () => {};
76+
}
8377

84-
return spotApiEndpoint;
85-
},
86-
(endpoint) => {
87-
if (!endpoint) {
88-
store.dispatch(setSolPrice(loadableIdle()));
89-
return () => {};
78+
const spotApiClient = new SpotApiClient(params.endpoint);
79+
80+
const observer = new QueryObserver(appQueryClient, {
81+
queryKey: ['spot', 'solPrice'],
82+
queryFn: wrapAndLogBonsaiError(
83+
() => spotApiClient.getTokenPrice(SOL_MINT_ADDRESS),
84+
'solPrice'
85+
),
86+
refetchInterval: timeUnits.second * 10,
87+
staleTime: timeUnits.second * 10,
88+
retry: 5,
89+
retryDelay: (attempt) => timeUnits.second * 3 * 2 ** attempt,
90+
});
91+
92+
const unsubscribe = safeSubscribeObserver(observer, (result) => {
93+
try {
94+
store.dispatch(setSolPrice(queryResultToLoadable(result)));
95+
} catch (e) {
96+
logBonsaiError('setUpSolPriceQuery', 'Error handling result from react query', e, result);
9097
}
98+
});
9199

92-
const spotApiClient = new SpotApiClient(endpoint);
93-
94-
const observer = new QueryObserver(appQueryClient, {
95-
queryKey: ['spot', 'solPrice'],
96-
queryFn: wrapAndLogBonsaiError(
97-
() => spotApiClient.getTokenPrice(SOL_MINT_ADDRESS),
98-
'solPrice'
99-
),
100-
refetchInterval: timeUnits.second * 10,
101-
staleTime: timeUnits.second * 10,
102-
retry: 5,
103-
retryDelay: (attempt) => timeUnits.second * 3 * 2 ** attempt,
104-
});
105-
106-
const unsubscribe = safeSubscribeObserver(observer, (result) => {
107-
try {
108-
store.dispatch(
109-
setSolPrice(mapLoadableData(queryResultToLoadable(result), (data) => data.price))
110-
);
111-
} catch (e) {
112-
logBonsaiError('setUpSolPriceQuery', 'Error handling result from react query', e, result);
113-
}
114-
});
115-
116-
return () => {
117-
store.dispatch(setSolPrice(loadableIdle()));
118-
unsubscribe();
119-
};
120-
}
121-
);
100+
return () => {
101+
store.dispatch(setSolPrice(loadableIdle()));
102+
unsubscribe();
103+
};
104+
});
122105
}

src/bonsai/selectors/spot.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
selectRawTokenMetadataLoading,
88
} from './base';
99

10-
export const selectSolPrice = createAppSelector([selectRawSolPrice], (solPrice) => solPrice);
10+
export const selectSolPrice = createAppSelector([selectRawSolPrice], (solPrice) => solPrice?.price);
1111

1212
export const selectSolPriceLoading = createAppSelector(
1313
[selectRawSolPriceLoading],
@@ -16,7 +16,7 @@ export const selectSolPriceLoading = createAppSelector(
1616

1717
export const selectTokenMetadata = createAppSelector(
1818
[selectRawTokenMetadata],
19-
(tokenMetadata) => tokenMetadata
19+
(tokenMetadata) => tokenMetadata?.tokenInfo
2020
);
2121

2222
export const selectTokenMetadataLoading = createAppSelector(

src/clients/spotApi.ts

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,31 @@ export type SpotTokenPriceResponse = {
66
price: number;
77
};
88

9+
export type TokenInfo = {
10+
priceUSD: number;
11+
priceSOL: number;
12+
marketCapUSD: number;
13+
marketCapSOL: number;
14+
pricePercentChange1h: number;
15+
token1hPriceChange: string;
16+
pricePercentChange24h: number;
17+
token24hPriceChange: string;
18+
volumeUSD: number;
19+
tokenVolume: string;
20+
liquidityUSD: number;
21+
liquiditySOL: number;
22+
tokenLiquidity: string;
23+
token24hBuys: number;
24+
token24hSells: number;
25+
isPump: boolean;
26+
bondingCurveProgress: number;
27+
tokenFDV: string;
28+
isGraduating: boolean;
29+
tokenDexUrl: string;
30+
};
31+
932
export type SpotTokenMetadataResponse = {
10-
tokenInfo: {
11-
priceUSD: number;
12-
priceSOL: number;
13-
marketCapUSD: number;
14-
marketCapSOL: number;
15-
pricePercentChange1h: number;
16-
token1hPriceChange: string;
17-
pricePercentChange24h: number;
18-
token24hPriceChange: string;
19-
volumeUSD: number;
20-
tokenVolume: string;
21-
liquidityUSD: number;
22-
liquiditySOL: number;
23-
tokenLiquidity: string;
24-
token24hBuys: number;
25-
token24hSells: number;
26-
isPump: boolean;
27-
bondingCurveProgress: number;
28-
tokenFDV: string;
29-
isGraduating: boolean;
30-
tokenDexUrl: string;
31-
};
33+
tokenInfo: TokenInfo;
3234
};
3335

3436
export class SpotApiClient {
@@ -42,15 +44,15 @@ export class SpotApiClient {
4244
this.host = host;
4345
}
4446

45-
_get(endpoint: string) {
46-
return simpleFetch(`${this.host}/${endpoint}`);
47+
_get<T>(endpoint: string) {
48+
return simpleFetch<T>(`${this.host}/${endpoint}`);
4749
}
4850

49-
async getTokenPrice(mint: string): Promise<SpotTokenPriceResponse> {
50-
return this._get(`tokens/price?mint=${mint}`);
51+
async getTokenPrice(mint: string) {
52+
return this._get<SpotTokenPriceResponse>(`tokens/price?mint=${mint}`);
5153
}
5254

53-
async getTokenMetadata(mint: string): Promise<SpotTokenMetadataResponse> {
54-
return this._get(`tokens/info?mint=${mint}`);
55+
async getTokenMetadata(mint: string) {
56+
return this._get<SpotTokenMetadataResponse>(`tokens/info?mint=${mint}`);
5557
}
5658
}

src/state/raw.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {
3131
IndexerSparklineResponseObject,
3232
} from '@/types/indexer/indexerManual';
3333

34-
import { SpotTokenMetadataResponse } from '@/clients/spotApi';
34+
import { SpotTokenMetadataResponse, SpotTokenPriceResponse } from '@/clients/spotApi';
3535
import { calc } from '@/lib/do';
3636

3737
import { autoBatchAllReducers } from './autoBatchHelpers';
@@ -101,7 +101,7 @@ export interface RawDataState {
101101
price: Loadable<TokenPriceResponse | undefined>;
102102
};
103103
spot: {
104-
solPrice: Loadable<number | undefined>;
104+
solPrice: Loadable<SpotTokenPriceResponse | undefined>;
105105
tokenMetadata: Loadable<SpotTokenMetadataResponse | undefined>;
106106
};
107107
}
@@ -232,7 +232,7 @@ export const rawSlice = createSlice({
232232
) => {
233233
state.rewards.price = action.payload;
234234
},
235-
setSolPrice: (state, action: PayloadAction<Loadable<number | undefined>>) => {
235+
setSolPrice: (state, action: PayloadAction<Loadable<SpotTokenPriceResponse | undefined>>) => {
236236
state.spot.solPrice = action.payload;
237237
},
238238
setTokenMetadata: (

0 commit comments

Comments
 (0)