A complete blockchain implementation featuring: - synord: Full node with GHOSTDAG consensus - explorer-web: Modern React blockchain explorer with 3D DAG visualization - CLI wallet and tools - Smart contract SDK and example contracts (DEX, NFT, token) - WASM crypto library for browser/mobile
115 lines
2.9 KiB
TypeScript
115 lines
2.9 KiB
TypeScript
/**
|
|
* Infinite loading hook for blocks.
|
|
* Accumulates blocks across multiple pages for virtual scrolling.
|
|
*/
|
|
|
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
import { api } from '../lib/api';
|
|
import type { ExplorerBlock } from '../lib/types';
|
|
|
|
interface UseInfiniteBlocksResult {
|
|
blocks: ExplorerBlock[];
|
|
isLoading: boolean;
|
|
isLoadingMore: boolean;
|
|
error: Error | null;
|
|
hasMore: boolean;
|
|
loadMore: () => Promise<void>;
|
|
reset: () => void;
|
|
total: number;
|
|
}
|
|
|
|
interface UseInfiniteBlocksOptions {
|
|
pageSize?: number;
|
|
initialLoad?: boolean;
|
|
}
|
|
|
|
export function useInfiniteBlocks(
|
|
options: UseInfiniteBlocksOptions = {}
|
|
): UseInfiniteBlocksResult {
|
|
const { pageSize = 50, initialLoad = true } = options;
|
|
|
|
const [blocks, setBlocks] = useState<ExplorerBlock[]>([]);
|
|
const [isLoading, setIsLoading] = useState(initialLoad);
|
|
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
const [error, setError] = useState<Error | null>(null);
|
|
const [hasMore, setHasMore] = useState(true);
|
|
const [total, setTotal] = useState(0);
|
|
|
|
// Track current page to avoid duplicate fetches
|
|
const currentPageRef = useRef(0);
|
|
const isFetchingRef = useRef(false);
|
|
|
|
const fetchPage = useCallback(
|
|
async (page: number, isInitial = false) => {
|
|
if (isFetchingRef.current) return;
|
|
isFetchingRef.current = true;
|
|
|
|
try {
|
|
if (isInitial) {
|
|
setIsLoading(true);
|
|
} else {
|
|
setIsLoadingMore(true);
|
|
}
|
|
setError(null);
|
|
|
|
const response = await api.getBlocks(page, pageSize);
|
|
|
|
setBlocks((prev) => {
|
|
// For initial load, replace blocks
|
|
if (isInitial) return response.data;
|
|
// For subsequent loads, append (avoiding duplicates)
|
|
const existingHashes = new Set(prev.map((b) => b.hash));
|
|
const newBlocks = response.data.filter(
|
|
(b) => !existingHashes.has(b.hash)
|
|
);
|
|
return [...prev, ...newBlocks];
|
|
});
|
|
|
|
setTotal(response.total);
|
|
setHasMore(response.hasNext);
|
|
currentPageRef.current = page;
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e : new Error(String(e)));
|
|
} finally {
|
|
setIsLoading(false);
|
|
setIsLoadingMore(false);
|
|
isFetchingRef.current = false;
|
|
}
|
|
},
|
|
[pageSize]
|
|
);
|
|
|
|
// Initial load
|
|
useEffect(() => {
|
|
if (initialLoad) {
|
|
fetchPage(1, true);
|
|
}
|
|
}, [fetchPage, initialLoad]);
|
|
|
|
const loadMore = useCallback(async () => {
|
|
if (isFetchingRef.current || !hasMore) return;
|
|
const nextPage = currentPageRef.current + 1;
|
|
await fetchPage(nextPage);
|
|
}, [fetchPage, hasMore]);
|
|
|
|
const reset = useCallback(() => {
|
|
setBlocks([]);
|
|
setHasMore(true);
|
|
setTotal(0);
|
|
currentPageRef.current = 0;
|
|
fetchPage(1, true);
|
|
}, [fetchPage]);
|
|
|
|
return {
|
|
blocks,
|
|
isLoading,
|
|
isLoadingMore,
|
|
error,
|
|
hasMore,
|
|
loadMore,
|
|
reset,
|
|
total,
|
|
};
|
|
}
|
|
|
|
export default useInfiniteBlocks;
|