Skip to content

Commit

Permalink
Add pagination to the balances hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
ilamanov committed Jan 23, 2025
1 parent 6847152 commit d4094ed
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 58 deletions.
101 changes: 100 additions & 1 deletion __tests__/hooks/useEvmTokenBalances.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { renderHook, waitFor } from "@testing-library/react";
import { renderHook, act, waitFor } from "@testing-library/react";
import { DuneProvider } from "../../src/provider";
import { useEvmTokenBalances } from "../../src/evm/useEvmTokenBalances";
import { fetchEvmBalances } from "../../src/evm/duneApi";
Expand Down Expand Up @@ -33,6 +33,11 @@ describe("useTokenBalances", () => {
data: null,
error: null,
isLoading: false,
nextOffset: null,
offsets: [],
currentPage: 0,
nextPage: expect.any(Function),
previousPage: expect.any(Function),
});
});

Expand Down Expand Up @@ -124,6 +129,11 @@ describe("useTokenBalances", () => {
data: null,
error: null,
isLoading: false,
nextOffset: null,
offsets: [],
currentPage: 0,
nextPage: expect.any(Function),
previousPage: expect.any(Function),
});
});

Expand All @@ -135,6 +145,95 @@ describe("useTokenBalances", () => {
data: null,
error: null,
isLoading: false,
nextOffset: null,
offsets: [],
currentPage: 0,
nextPage: expect.any(Function),
previousPage: expect.any(Function),
});
});

it("should handle pagination: next and previous pages", async () => {
const walletAddress = "0x1234567890abcdef1234567890abcdef12345678";

const page1Response = {
request_time: "2025-01-16T18:09:37.116ZZ",
response_time: "2025-01-16T18:09:37.156ZZ",
wallet_address: walletAddress,
next_offset: "offset1",
balances: [
{
chain: "ethereum",
chain_id: 1,
address: "native",
amount: "121458493673814687",
symbol: "ETH",
decimals: 18,
price_usd: 3344.858473355283,
value_usd: 406.26147172582813,
},
],
};
const page2Response = {
request_time: "2025-01-16T18:09:37.116ZZ",
response_time: "2025-01-16T18:09:37.156ZZ",
wallet_address: walletAddress,
next_offset: "offset2",
balances: [
{
chain: "base",
chain_id: 8453,
address: "0x0000000000000000000000000000000000000000",
amount: "121458493673814687",
symbol: "ETH",
decimals: 18,
price_usd: 3344.858473355283,
value_usd: 406.26147172582813,
},
],
};

mockFetchEvmBalances
.mockResolvedValueOnce(page1Response)
.mockResolvedValueOnce(page2Response)
.mockResolvedValueOnce(page1Response);

const { result } = renderHook(() => useEvmTokenBalances(walletAddress), {
wrapper,
});

// Wait for the first page
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.data).toEqual(page1Response);
expect(result.current.currentPage).toBe(0);

// Fetch the next page
act(() => {
result.current.nextPage();
});

// Wait for the second page
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.data).toEqual(page2Response);
expect(result.current.currentPage).toBe(1);

// Fetch the previous page
act(() => {
result.current.previousPage();
});

// Wait for the first page again
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.data).toEqual(page1Response);
expect(result.current.currentPage).toBe(0);
});
});
96 changes: 95 additions & 1 deletion __tests__/hooks/useSvmTokenBalances.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { renderHook, waitFor } from "@testing-library/react";
import { renderHook, act, waitFor } from "@testing-library/react";
import { DuneProvider } from "../../src/provider";
import { useSvmTokenBalances } from "../../src/svm/useSvmTokenBalances";
import { fetchSvmBalances } from "../../src/svm/duneApi";
Expand Down Expand Up @@ -123,6 +123,11 @@ describe("useTokenBalances", () => {
data: null,
error: null,
isLoading: false,
nextOffset: null,
offsets: [],
currentPage: 0,
nextPage: expect.any(Function),
previousPage: expect.any(Function),
});
});

Expand All @@ -136,6 +141,95 @@ describe("useTokenBalances", () => {
data: null,
error: null,
isLoading: false,
nextOffset: null,
offsets: [],
currentPage: 0,
nextPage: expect.any(Function),
previousPage: expect.any(Function),
});
});

it("should handle pagination: next and previous pages", async () => {
const walletAddress = "0x1234567890abcdef1234567890abcdef12345678";

const page1Response = {
request_time: "2025-01-16T18:09:37.116ZZ",
response_time: "2025-01-16T18:09:37.156ZZ",
wallet_address: walletAddress,
next_offset: "offset1",
balances: [
{
chain: "ethereum",
chain_id: 1,
address: "native",
amount: "121458493673814687",
symbol: "ETH",
decimals: 18,
price_usd: 3344.858473355283,
value_usd: 406.26147172582813,
},
],
};
const page2Response = {
request_time: "2025-01-16T18:09:37.116ZZ",
response_time: "2025-01-16T18:09:37.156ZZ",
wallet_address: walletAddress,
next_offset: "offset2",
balances: [
{
chain: "base",
chain_id: 8453,
address: "0x0000000000000000000000000000000000000000",
amount: "121458493673814687",
symbol: "ETH",
decimals: 18,
price_usd: 3344.858473355283,
value_usd: 406.26147172582813,
},
],
};

mockFetchSvmBalances
.mockResolvedValueOnce(page1Response)
.mockResolvedValueOnce(page2Response)
.mockResolvedValueOnce(page1Response);

const { result } = renderHook(() => useSvmTokenBalances(walletAddress), {
wrapper,
});

// Wait for the first page
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.data).toEqual(page1Response);
expect(result.current.currentPage).toBe(0);

// Fetch the next page
act(() => {
result.current.nextPage();
});

// Wait for the second page
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.data).toEqual(page2Response);
expect(result.current.currentPage).toBe(1);

// Fetch the previous page
act(() => {
result.current.previousPage();
});

// Wait for the first page again
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.data).toEqual(page1Response);
expect(result.current.currentPage).toBe(0);
});
});
1 change: 1 addition & 0 deletions src/evm/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type BalanceData = {
request_time: string;
response_time: string;
wallet_address: string;
next_offset?: string | null;
balances: TokenBalance[];
};

Expand Down
105 changes: 77 additions & 28 deletions src/evm/useEvmTokenBalances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,95 @@ export const useEvmTokenBalances = (
data: BalanceData | null;
error: FetchError | null;
isLoading: boolean;
nextOffset: string | null; // Track next_offset
offsets: string[]; // Store offsets for each page
currentPage: number; // Track the current page
}>({
data: null,
error: null,
isLoading: false,
nextOffset: null, // Next offset from the API
offsets: [], // List of offsets corresponding to pages
currentPage: 0, // Start at the first page
});

const memoizedParams = useDeepMemo(() => params, params);
const apiKey = useGetApiKey();

// Function to fetch data for a specific page
const fetchDataAsync = async (offset: string | null) => {
if (!apiKey || !walletAddress || !isAddress(walletAddress)) return;

setState((prevState) => ({ ...prevState, isLoading: true }));

try {
// Convert offset to number or undefined
const updatedParams = {
...memoizedParams,
offset: offset ?? undefined,
};

const result = await fetchEvmBalances(
walletAddress,
updatedParams,
apiKey
);

setState((prevState) => ({
...prevState,
data: result,
error: null,
isLoading: false,
nextOffset: result.next_offset || null,
offsets: offset ? [...prevState.offsets, offset] : prevState.offsets,
}));
} catch (err) {
setState({
data: null,
error: err as FetchError,
isLoading: false,
nextOffset: null,
offsets: [],
currentPage: 0,
});
}
};

// Trigger fetch when walletAddress or params change
useEffect(() => {
if (!apiKey) return;
const fetchDataAsync = async () => {
if (!walletAddress || !isAddress(walletAddress)) return;

setState((prevState) => ({ ...prevState, isLoading: true }));

try {
const result = await fetchEvmBalances(
walletAddress,
memoizedParams,
apiKey
);
setState({
data: result,
error: null,
isLoading: false,
});
} catch (err) {
setState({
data: null,
error: err as FetchError,
isLoading: false,
});
}
};

fetchDataAsync();
// Fetch the first page on initial load or when walletAddress changes
fetchDataAsync(null);
}, [walletAddress, memoizedParams, apiKey]);

return state;
// Function to go to the next page
const nextPage = () => {
if (state.nextOffset) {
fetchDataAsync(state.nextOffset); // Fetch using the next offset
setState((prevState) => ({
...prevState,
currentPage: prevState.currentPage + 1, // Update page number
}));
}
};

// Function to go to the previous page
const previousPage = () => {
if (state.currentPage > 0) {
// Use the offset corresponding to the previous page
const previousOffset = state.offsets[state.currentPage - 1];
fetchDataAsync(previousOffset);
setState((prevState) => ({
...prevState,
currentPage: prevState.currentPage - 1,
}));
}
};

return {
...state,
nextPage,
previousPage,
};
};

/** @deprecated */
Expand Down
1 change: 1 addition & 0 deletions src/svm/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type BalanceData = {
response_time: string;
wallet_address: string;
balances: TokenBalance[];
next_offset?: string | null;
};

export type FetchError = Error & {
Expand Down
Loading

0 comments on commit d4094ed

Please sign in to comment.