diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index b024313ac6..eb3696a78d 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -49,31 +49,41 @@ export function TransactionDetailsPage({ signature }: Props) { function StatusCard({ signature }: Props) { const fetchStatus = useFetchTransactionStatus(); const status = useTransactionStatus(signature); - const refresh = useFetchTransactionStatus(); + const fetchDetails = useFetchTransactionDetails(); const details = useTransactionDetails(signature); const { firstAvailableBlock, status: clusterStatus } = useCluster(); + const refresh = React.useCallback( + (signature: string) => { + fetchStatus(signature); + fetchDetails(signature); + }, + [fetchStatus, fetchDetails] + ); // Fetch transaction on load React.useEffect(() => { - if (!status && clusterStatus === ClusterStatus.Connected) + if (!status && clusterStatus === ClusterStatus.Connected) { fetchStatus(signature); + } }, [signature, clusterStatus]); // eslint-disable-line react-hooks/exhaustive-deps if (!status || status.fetchStatus === FetchStatus.Fetching) { return ; } else if (status?.fetchStatus === FetchStatus.FetchFailed) { - return refresh(signature)} text="Fetch Failed" />; + return ( + fetchStatus(signature)} text="Fetch Failed" /> + ); } else if (!status.info) { if (firstAvailableBlock !== undefined) { return ( refresh(signature)} + retry={() => fetchStatus(signature)} text="Not Found" subtext={`Note: Transactions processed before block ${firstAvailableBlock} are not available at this time`} /> ); } - return refresh(signature)} text="Not Found" />; + return fetchStatus(signature)} text="Not Found" />; } const { info } = status; @@ -187,9 +197,8 @@ function StatusCard({ signature }: Props) { } function AccountsCard({ signature }: Props) { - const details = useTransactionDetails(signature); - const { url } = useCluster(); + const details = useTransactionDetails(signature); const fetchStatus = useFetchTransactionStatus(); const fetchDetails = useFetchTransactionDetails(); const refreshStatus = () => fetchStatus(signature); @@ -198,6 +207,13 @@ function AccountsCard({ signature }: Props) { const message = transaction?.message; const status = useTransactionStatus(signature); + // Fetch details on load + React.useEffect(() => { + if (status?.info?.confirmations === "max" && !details) { + fetchDetails(signature); + } + }, [signature, details, status, fetchDetails]); + if (!status || !status.info) { return null; } else if (!details) { diff --git a/explorer/src/providers/accounts/index.tsx b/explorer/src/providers/accounts/index.tsx index f34f96c561..45a35f0f41 100644 --- a/explorer/src/providers/accounts/index.tsx +++ b/explorer/src/providers/accounts/index.tsx @@ -29,6 +29,7 @@ export interface Account { type Accounts = { [address: string]: Account }; interface State { accounts: Accounts; + url: string; } export enum ActionType { @@ -39,6 +40,7 @@ export enum ActionType { interface Update { type: ActionType.Update; + url: string; pubkey: PublicKey; data: { status: FetchStatus; @@ -49,17 +51,25 @@ interface Update { interface Fetch { type: ActionType.Fetch; + url: string; pubkey: PublicKey; } interface Clear { type: ActionType.Clear; + url: string; } type Action = Update | Fetch | Clear; type Dispatch = (action: Action) => void; function reducer(state: State, action: Action): State { + if (action.type === ActionType.Clear) { + return { url: action.url, accounts: {} }; + } else if (action.url !== state.url) { + return state; + } + switch (action.type) { case ActionType.Fetch: { const address = action.pubkey.toBase58(); @@ -100,13 +110,6 @@ function reducer(state: State, action: Action): State { } break; } - - case ActionType.Clear: { - return { - ...state, - accounts: {}, - }; - } } return state; } @@ -116,14 +119,15 @@ const DispatchContext = React.createContext(undefined); type AccountsProviderProps = { children: React.ReactNode }; export function AccountsProvider({ children }: AccountsProviderProps) { + const { url } = useCluster(); const [state, dispatch] = React.useReducer(reducer, { + url, accounts: {}, }); // Clear account statuses whenever cluster is changed - const { url } = useCluster(); React.useEffect(() => { - dispatch({ type: ActionType.Clear }); + dispatch({ type: ActionType.Clear, url }); }, [url]); return ( @@ -145,6 +149,7 @@ async function fetchAccountInfo( dispatch({ type: ActionType.Fetch, pubkey, + url, }); let fetchStatus; @@ -182,7 +187,7 @@ async function fetchAccountInfo( fetchStatus = FetchStatus.FetchFailed; } const data = { status: fetchStatus, lamports, details }; - dispatch({ type: ActionType.Update, data, pubkey }); + dispatch({ type: ActionType.Update, data, pubkey, url }); } export function useAccounts() { diff --git a/explorer/src/providers/transactions/details.tsx b/explorer/src/providers/transactions/details.tsx index 0427d3fa77..d20775bd15 100644 --- a/explorer/src/providers/transactions/details.tsx +++ b/explorer/src/providers/transactions/details.tsx @@ -5,7 +5,7 @@ import { ParsedConfirmedTransaction, } from "@solana/web3.js"; import { useCluster } from "../cluster"; -import { useTransactions, FetchStatus } from "./index"; +import { FetchStatus } from "./index"; import { CACHED_DETAILS, isCached } from "./cached"; export interface Details { @@ -13,68 +13,69 @@ export interface Details { transaction: ParsedConfirmedTransaction | null; } -type State = { [signature: string]: Details }; +type State = { + entries: { [signature: string]: Details }; + url: string; +}; export enum ActionType { Update, - Add, Clear, } interface Update { type: ActionType.Update; + url: string; signature: string; fetchStatus: FetchStatus; transaction: ParsedConfirmedTransaction | null; } -interface Add { - type: ActionType.Add; - signature: TransactionSignature; -} - interface Clear { type: ActionType.Clear; + url: string; } -type Action = Update | Add | Clear; +type Action = Update | Clear; type Dispatch = (action: Action) => void; function reducer(state: State, action: Action): State { - switch (action.type) { - case ActionType.Add: { - const details = { ...state }; - const signature = action.signature; - if (!details[signature]) { - details[signature] = { - fetchStatus: FetchStatus.Fetching, - transaction: null, - }; - } - return details; - } + if (action.type === ActionType.Clear) { + return { url: action.url, entries: {} }; + } else if (action.url !== state.url) { + return state; + } + switch (action.type) { case ActionType.Update: { - let details = state[action.signature]; + const signature = action.signature; + const details = state.entries[signature]; if (details) { - details = { - ...details, - fetchStatus: action.fetchStatus, - transaction: action.transaction, - }; return { ...state, - [action.signature]: details, + entries: { + ...state.entries, + [signature]: { + ...details, + fetchStatus: action.fetchStatus, + transaction: action.transaction, + }, + }, + }; + } else { + return { + ...state, + entries: { + ...state.entries, + [signature]: { + fetchStatus: FetchStatus.Fetching, + transaction: null, + }, + }, }; } - break; - } - - case ActionType.Clear: { - return {}; } } - return state; } export const StateContext = React.createContext(undefined); @@ -84,28 +85,13 @@ export const DispatchContext = React.createContext( type DetailsProviderProps = { children: React.ReactNode }; export function DetailsProvider({ children }: DetailsProviderProps) { - const [state, dispatch] = React.useReducer(reducer, {}); - const { transactions, lastFetched } = useTransactions(); const { url } = useCluster(); + const [state, dispatch] = React.useReducer(reducer, { url, entries: {} }); React.useEffect(() => { - dispatch({ type: ActionType.Clear }); + dispatch({ type: ActionType.Clear, url }); }, [url]); - // Filter blocks for current transaction slots - React.useEffect(() => { - if (lastFetched) { - const confirmed = - transactions[lastFetched] && - transactions[lastFetched].info?.confirmations === "max"; - const noDetails = !state[lastFetched]; - if (confirmed && noDetails) { - dispatch({ type: ActionType.Add, signature: lastFetched }); - fetchDetails(dispatch, lastFetched, url); - } - } - }, [transactions, lastFetched]); // eslint-disable-line react-hooks/exhaustive-deps - return ( @@ -125,6 +111,7 @@ async function fetchDetails( fetchStatus: FetchStatus.Fetching, transaction: null, signature, + url, }); let fetchStatus; @@ -143,7 +130,13 @@ async function fetchDetails( fetchStatus = FetchStatus.FetchFailed; } } - dispatch({ type: ActionType.Update, fetchStatus, signature, transaction }); + dispatch({ + type: ActionType.Update, + fetchStatus, + signature, + transaction, + url, + }); } export function useFetchTransactionDetails() { diff --git a/explorer/src/providers/transactions/index.tsx b/explorer/src/providers/transactions/index.tsx index 77aa77cae4..6834824b94 100644 --- a/explorer/src/providers/transactions/index.tsx +++ b/explorer/src/providers/transactions/index.tsx @@ -44,7 +44,7 @@ export interface TransactionStatus { type Transactions = { [signature: string]: TransactionStatus }; interface State { transactions: Transactions; - lastFetched: TransactionSignature | undefined; + url: string; } export enum ActionType { @@ -55,6 +55,7 @@ export enum ActionType { interface UpdateStatus { type: ActionType.UpdateStatus; + url: string; signature: TransactionSignature; fetchStatus: FetchStatus; info?: TransactionStatusInfo; @@ -62,17 +63,25 @@ interface UpdateStatus { interface FetchSignature { type: ActionType.FetchSignature; + url: string; signature: TransactionSignature; } interface Clear { type: ActionType.Clear; + url: string; } type Action = UpdateStatus | FetchSignature | Clear; type Dispatch = (action: Action) => void; function reducer(state: State, action: Action): State { + if (action.type === ActionType.Clear) { + return { url: action.url, transactions: {} }; + } else if (action.url !== state.url) { + return state; + } + switch (action.type) { case ActionType.FetchSignature: { const signature = action.signature; @@ -86,7 +95,7 @@ function reducer(state: State, action: Action): State { info: undefined, }, }; - return { ...state, transactions, lastFetched: signature }; + return { ...state, transactions }; } else { const transactions = { ...state.transactions, @@ -95,7 +104,7 @@ function reducer(state: State, action: Action): State { fetchStatus: FetchStatus.Fetching, }, }; - return { ...state, transactions, lastFetched: signature }; + return { ...state, transactions }; } } @@ -114,13 +123,6 @@ function reducer(state: State, action: Action): State { } break; } - - case ActionType.Clear: { - return { - ...state, - transactions: {}, - }; - } } return state; } @@ -132,12 +134,12 @@ const DispatchContext = React.createContext(undefined); type TransactionsProviderProps = { children: React.ReactNode }; export function TransactionsProvider({ children }: TransactionsProviderProps) { + const { cluster, status: clusterStatus, url } = useCluster(); const [state, dispatch] = React.useReducer(reducer, { transactions: {}, - lastFetched: undefined, + url, }); - const { cluster, status: clusterStatus, url } = useCluster(); const fetchAccount = useFetchAccountInfo(); const query = useQuery(); const testFlag = query.get("test"); @@ -145,9 +147,7 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) { // Check transaction statuses whenever cluster updates React.useEffect(() => { if (clusterStatus === ClusterStatus.Connecting) { - dispatch({ type: ActionType.Clear }); - } else if (clusterStatus === ClusterStatus.Connected && state.lastFetched) { - fetchTransactionStatus(dispatch, state.lastFetched, url); + dispatch({ type: ActionType.Clear, url }); } // Create a test transaction @@ -216,6 +216,7 @@ export async function fetchTransactionStatus( dispatch({ type: ActionType.FetchSignature, signature, + url, }); let fetchStatus; @@ -261,6 +262,7 @@ export async function fetchTransactionStatus( signature, fetchStatus, info, + url, }); } @@ -295,7 +297,7 @@ export function useTransactionDetails(signature: TransactionSignature) { ); } - return context[signature]; + return context.entries[signature]; } export function useFetchTransactionStatus() {