Skip to content

Commit e3b852b

Browse files
committed
Implement fetch() wrapper to get correct type in DataState, instead of any or Response
1 parent 9100cd5 commit e3b852b

File tree

7 files changed

+51
-32
lines changed

7 files changed

+51
-32
lines changed

src/components/content/block-page/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export default function BlockPage(props: { network: string }) {
5151
setFinalizedError(error);
5252
}
5353
try {
54-
const res = await fetch(blockRewardUrl);
54+
const res = await fetch(blockRewardUrl!);
5555
if (!res.ok) throw new Error(`Response NOT OK, status: ${res.status}`);
5656
const data = await res.json();
5757
if (!data.result.blockReward) throw new Error('Block reward missing from response.');

src/components/content/gastracker-page/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { ChartBarIcon } from '@heroicons/react/24/outline';
44
import GastrackerCard from './gastracker-card';
55
import BreakMobile from '@/components/common/break-mobile';
66
import PageWrapper from '@/components/common/page-wrapper';
7-
import { useDataState } from '@/lib/data-state';
7+
import { fetchJson, useDataState } from '@/lib/data-state';
88

99

1010
export default function GastrackerPage() {
1111
const pricesData = useDataState<any>({
12-
fetcher: (url) => fetch(url),
12+
fetcher: (url) => fetchJson(url),
1313
args: ['https://eth.blockscout.com/api/v2/stats'],
1414
});
1515

src/components/content/home-page/blocks/block-reward.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1-
import { useDataState, useArgs } from '@/lib/data-state';
1+
import { useDataState, fetchJson } from '@/lib/data-state';
22
import { getBlockRewardUrl, getEtherValueFromWei } from '@/lib/utilities';
33
import LoadingPulse from '@/components/common/indicators/loading-pulse';
44
import LoadingPulseStatic from '@/components/common/indicators/loading-pulse-static';
55
import ErrorWithRefetch from '@/components/common/indicators/error-with-refetch';
66
import ValueWithRefetch from '@/components/common/indicators/value-with-refetch';
7+
import { BigNumber } from 'alchemy-sdk';
78

89

10+
type BlockRewardData = {
11+
blockReward: BigNumber,
12+
};
13+
914
export default function BlockReward(props: {
1015
id: number,
1116
network: string,
1217
blockNumber?: number,
1318
}) {
14-
const blockRewardData = useDataState<any>({
15-
fetcher: (url) => fetch(url),
19+
const blockRewardData = useDataState<BlockRewardData, [string?]>({
20+
fetcher: (url) => fetchJson(url!),
1621
args: [getBlockRewardUrl(props.network, props.blockNumber)],
1722
});
1823

@@ -21,7 +26,7 @@ export default function BlockReward(props: {
2126

2227
let errorFallback = <ErrorWithRefetch refetch={blockRewardData.fetch} />;
2328
// Latest Block often doesn't have a reward yet:
24-
if (props.id === 0 && blockRewardData.error && blockRewardData.error.message === 'Empty response') {
29+
if (props.id === 0 && blockRewardData.error && blockRewardData.error.message === 'Empty json response') {
2530
errorFallback = <ValueWithRefetch refetch={blockRewardData.fetch} value='TBD'/>;
2631
}
2732

src/components/content/home-page/blocks/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default function Blocks(props: {
1818
<ErrorWithRefetch
1919
error='Error getting latest block'
2020
className='pl-4 py-2'
21-
refetch={props.latestBlockData.refetch}
21+
refetch={props.latestBlockData.fetch}
2222
/>
2323
:
2424
[...Array(5)].map((_, i) =>

src/components/content/home-page/stats/index.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,31 @@ import { ClipboardDocumentListIcon } from '@heroicons/react/24/outline';
99
import Link from 'next/link';
1010
import StatCard from './stat-card';
1111
import { getGasPriceGwei, getGasPriceUsd } from '@/lib/utilities';
12-
import { useDataState, useArgs, DataState } from '@/lib/data-state';
12+
import { useDataState, DataState, fetchJson } from '@/lib/data-state';
1313

1414

15+
type EthSupplyData = {
16+
available_supply: string;
17+
}
18+
19+
type PricesAndTxsData = {
20+
coin_price: string;
21+
gas_prices: {
22+
average: number;
23+
};
24+
transactions_today: string;
25+
total_transactions: string;
26+
}
27+
1528
export default function Stats() {
16-
const ethSupplyData = useDataState<any>({
17-
fetcher: (url) => fetch(url),
29+
const ethSupplyData = useDataState<EthSupplyData, [string]>({
30+
fetcher: (url) => fetchJson(url),
1831
args: ['https://eth.blockscout.com/api/v2/stats/charts/market'],
1932
});
2033
const ethSupply = ethSupplyData.value ? +ethSupplyData.value.available_supply : undefined;
2134

22-
const pricesAndTxsData = useDataState<any>({
23-
fetcher: (url) => fetch(url),
35+
const pricesAndTxsData = useDataState<PricesAndTxsData, [string]>({
36+
fetcher: (url) => fetchJson(url),
2437
args: ['https://eth.blockscout.com/api/v2/stats'],
2538
});
2639

src/components/content/home-page/stats/stat-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default function StatCard<T>(props: {
2323
<div className='pl-12'>
2424
<props.dataState.Render
2525
value={props.value}
26-
errorFallback={<ErrorWithRefetch refetch={props.dataState.refetch} />}
26+
errorFallback={<ErrorWithRefetch refetch={props.dataState.fetch} />}
2727
/>
2828
</div>
2929
</div>

src/lib/data-state.tsx

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -172,39 +172,40 @@ export function useDataState<T, A extends any[] = any[]>(
172172
// and attaches it to the DataState's internal DataRoot value that is
173173
// tracked and updated with `useState`:
174174
function useFetcher<T, A extends any[] = any[]>(
175-
fetcher: (...args: A) => Promise<T>,
175+
fetcher: (...args: A) => Promise<T> | Response,
176176
args: A,
177177
setRoot: Dispatch<SetStateAction<DataRoot<T>>>,
178-
): () => Promise<any> {
178+
): () => Promise<T | undefined> {
179179
return useCallback(async () => {
180180
// console.log('Fetch triggered with args: ', args);
181181
// Skip fetching if one of the arguments is still undefined:
182182
if (args?.some(arg => arg === undefined)) return;
183183

184-
let response;
185-
186184
try {
187-
response = await fetcher(...args);
188-
// console.log('response = ', response);
189-
190-
// If the function is fetch, call `.json()`:
191-
if (response instanceof Response) {
192-
if (!response.ok) throw new Error(`Fetch response NOT OK, status: ${response.status}`);
193-
const json = await response.json();
194-
if ('result' in json) response = json.result;
195-
else response = json;
196-
}
185+
let response = await fetcher(...args);
197186
if (!response) throw new Error('Empty response');
198-
setRoot(DataState.value(response));
187+
188+
const typedResponse = response as T;
189+
setRoot(DataState.value(typedResponse));
190+
return typedResponse;
199191
} catch (err) {
200192
setRoot(DataState.error(err));
201193
}
202-
203-
// console.log('response = ', response);
204-
return response;
205194
}, [fetcher, args, setRoot]);
206195
}
207196

197+
// To be used as a wrapper for fetch() inside useDataState inline fetcher definition:
198+
// (This allows the types to match with the DataState's, instead of returning a fetch Response type)
199+
export async function fetchJson<T>(url: string): Promise<T> {
200+
const response = await fetch(url);
201+
if (!response.ok) throw new Error(`Fetch failed, status: ${response.status}`);
202+
const json = await response.json();
203+
const result = 'result' in json ? json.result : json;
204+
if (!result) throw new Error('Empty json response');
205+
206+
return result as T;
207+
}
208+
208209
// Hook that memoizes the argument array to prevent a new array
209210
// from being created on every render:
210211
// (For inline use in `useDataState()` or `DataState.Init()` calls)

0 commit comments

Comments
 (0)