Skip to content

Commit 94d9151

Browse files
committed
SD-91: Improve modal performance
1 parent c2ee17e commit 94d9151

File tree

8 files changed

+111
-83
lines changed

8 files changed

+111
-83
lines changed

src/domains/misc/components/Modal/ModalProvider.tsx

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ import Modal from './Modal';
2020
type Modal = {
2121
id: string,
2222
modal: ReactElement,
23+
page?: number,
2324
};
2425

2526
type ModalContextType = {
26-
mount: (modal: Modal) => void,
27+
mount: (modal: Modal, options?: { checkDuplicateBy?: string[] }) => void,
2728
unmount: (id: string) => void,
29+
updateId: (oldId: string, newId: string) => Promise<Modal | null>,
30+
getModals: () => Modal[],
2831
modals: Modal[],
2932
};
3033

@@ -56,8 +59,31 @@ export const ModalProvider = ({ children }: { children: ReactNode }) => {
5659
setModals(prev => prev.filter(m => m.id !== id));
5760
}, []);
5861

62+
const updateId = useCallback(async (oldId: string, newId: string): Promise<Modal | null> => {
63+
return new Promise(resolve => {
64+
setModals(prev => {
65+
const modalIndex = prev.findIndex(modal => modal.id === oldId);
66+
67+
if (modalIndex === -1) {
68+
resolve(null);
69+
return prev;
70+
}
71+
72+
const updatedModal = { ...prev[modalIndex], id: newId };
73+
const updatedModals = prev.map((modal, index) =>
74+
index === modalIndex ? updatedModal : modal
75+
);
76+
77+
resolve(updatedModal);
78+
return updatedModals;
79+
});
80+
});
81+
}, []);
82+
83+
const getModals = () => modals;
84+
5985
return (
60-
<ModalContext.Provider value={{ mount, unmount, modals }}>
86+
<ModalContext.Provider value={{ mount, unmount, updateId, modals, getModals }}>
6187
{children}
6288
{createPortal(
6389
<AnimatePresence>
@@ -89,11 +115,15 @@ export const ModalProvider = ({ children }: { children: ReactNode }) => {
89115
);
90116
};
91117

92-
export const useModal = () => {
118+
export const useModals = () => {
93119
const ctx = useContext(ModalContext);
94-
if (!ctx) throw new Error('useModal must be used within ModalProvider');
120+
if (!ctx) throw new Error('useModals must be used within ModalProvider');
121+
122+
return ctx;
123+
};
95124

96-
const { mount, unmount, modals } = ctx;
125+
export const useModal = () => {
126+
const { mount, unmount, modals, updateId: _updateId } = useModals();
97127
const defaultId = useRef(uuidv4()).current;
98128
const currentIdRef = useRef<string>(defaultId);
99129

@@ -107,13 +137,17 @@ export const useModal = () => {
107137
unmount(currentIdRef.current);
108138
}, [unmount]);
109139

140+
const updateId = async (newId: string) => {
141+
return _updateId(currentIdRef.current, newId);
142+
};
143+
110144
const modal = modals.find(m => m.id === currentIdRef.current);
111145

112146
return {
113147
open,
114148
close,
115149
isOpen: !!modal,
116-
modals,
150+
updateId,
117151
};
118152
};
119153

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { ModalProvider, useModal, useModalControls } from './ModalProvider';
1+
export { ModalProvider, useModal, useModalControls, useModals } from './ModalProvider';
22
export { default } from './Modal';

src/domains/shielder/stores/getShielderIndexedDB.ts

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ export type LocalShielderActivityHistory = ShielderTransaction & {
2323
};
2424

2525
export type PartialLocalShielderActivityHistory =
26-
| (Partial<LocalShielderActivityHistory> & { localId: string })
27-
| (Partial<LocalShielderActivityHistory> & { txHash: `0x${string}` });
26+
| (Partial<LocalShielderActivityHistory> & { localId: string, txHash?: `0x${string}` })
27+
| (Partial<LocalShielderActivityHistory> & { txHash: `0x${string}`, localId?: string })
28+
| (Partial<LocalShielderActivityHistory> & { localId: string, txHash: `0x${string}` });
2829

2930
export type LocalShielderActivityHistoryArray = PartialLocalShielderActivityHistory[];
3031

@@ -170,39 +171,37 @@ export const getLocalShielderActivityHistoryIndexedDB = (accountAddress: string)
170171
}
171172
},
172173
upsertItem: async (chainId: number, activity: PartialLocalShielderActivityHistory) => {
173-
try {
174-
const db = await dbp;
175-
const chainKey = chainId.toString();
176-
const allChains = (await db.get(STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY, accountAddress)) ?? {};
177-
const existingRaw = allChains[chainKey];
178-
const existing = existingRaw ? fromActivityHistoryStorageFormat(existingRaw) : [];
174+
const db = await dbp;
175+
const chainKey = chainId.toString();
176+
const allChains = (await db.get(STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY, accountAddress)) ?? {};
177+
const existingRaw = allChains[chainKey];
178+
const existing = existingRaw ? fromActivityHistoryStorageFormat(existingRaw) : [];
179179

180-
const isSame = (a: PartialLocalShielderActivityHistory, b: PartialLocalShielderActivityHistory) =>
181-
(isPresent(a.localId) && a.localId === b.localId) ||
182-
(isPresent(a.txHash) && a.txHash === b.txHash);
180+
const isSame = (a: PartialLocalShielderActivityHistory, b: PartialLocalShielderActivityHistory) =>
181+
(isPresent(a.localId) && a.localId === b.localId) ||
182+
(isPresent(a.txHash) && a.txHash === b.txHash);
183183

184-
const matches = existing.filter(item => isSame(item, activity));
185-
const rest = existing.filter(item => !isSame(item, activity));
184+
const matches = existing.filter(item => isSame(item, activity));
185+
const rest = existing.filter(item => !isSame(item, activity));
186186

187-
const localOnly = matches.find(i => isPresent(i.localId) && !isPresent(i.txHash)) ?? {};
188-
const withTxHash = matches.find(i => isPresent(i.txHash)) ?? {};
187+
const localOnly = matches.find(i => isPresent(i.localId) && !isPresent(i.txHash)) ?? {};
188+
const withTxHash = matches.find(i => isPresent(i.txHash)) ?? {};
189189

190-
const merged = {
191-
...localOnly,
192-
...withTxHash,
193-
...activity,
194-
};
190+
const merged = {
191+
...localOnly,
192+
...withTxHash,
193+
...activity,
194+
};
195195

196-
const updated: LocalShielderActivityHistoryArray = [...rest, merged];
196+
const updated: LocalShielderActivityHistoryArray = [...rest, merged];
197197

198-
await db.put(
199-
STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY,
200-
{ ...allChains, [chainKey]: toActivityHistoryStorageFormat(updated) },
201-
accountAddress,
202-
);
203-
} catch (error) {
204-
console.error('Failed to update activity history in IndexedDB:', error);
205-
}
198+
await db.put(
199+
STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY,
200+
{ ...allChains, [chainKey]: toActivityHistoryStorageFormat(updated) },
201+
accountAddress,
202+
);
203+
204+
return merged;
206205
},
207206
};
208207
};

src/domains/shielder/utils/useShield.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import vars from 'src/domains/styling/utils/vars';
1717

1818
import useShielderClient from './useShielderClient';
1919

20-
export const useShield = () => {
20+
const useShield = () => {
2121
const { data: shielderClient } = useShielderClient();
2222
const { address: walletAddress, chainId } = useAccount();
2323
const publicClient = usePublicClient();

src/domains/shielder/utils/useShielderClient.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const useShielderClient = () => {
8585
};
8686
const timestamp = await getTimestamp();
8787

88-
await upsertTransaction(chainConfig.id, {
88+
const localTx = await upsertTransaction(chainConfig.id, {
8989
...tx,
9090
status: 'completed',
9191
completedTimestamp: timestamp,
@@ -97,7 +97,7 @@ const useShielderClient = () => {
9797
status: 'success',
9898
body: (
9999
<DetailsButton
100-
onClick={() => void openTransactionModal({ txHash: tx.txHash })}
100+
onClick={() => void openTransactionModal(localTx)}
101101
>
102102
See details
103103
</DetailsButton>

src/domains/shielder/utils/useTransactionDetailsModal.tsx

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,45 @@
11
import { useCallback } from 'react';
2-
import { Address } from 'viem';
32

3+
import { useModals } from 'src/domains/misc/components/Modal';
44
import TransactionDetailsModal from 'src/domains/shielder/components/TransactionDetailsModal';
5-
6-
import { useModal } from '../../misc/components/Modal';
7-
8-
import { useTransactionsHistory } from './useTransactionsHistory';
9-
10-
type TransactionIdentifier = {
11-
txHash: Address,
12-
} | {
13-
localId: string,
14-
};
5+
import { PartialLocalShielderActivityHistory } from 'src/domains/shielder/stores/getShielderIndexedDB';
6+
import { useTransactionsHistory } from 'src/domains/shielder/utils/useTransactionsHistory.tsx';
157

168
/**
179
* Custom hook to handle transaction details modal logic.
18-
* Prevents duplicate modals by using consistent modal IDs and handles
19-
* the transition from localId to txHash when transactions are confirmed.
10+
* Fetches the latest transaction data and opens a modal with up-to-date information.
11+
* Handles modal ID transitions when transactions move from pending (localId only)
12+
* to confirmed state (both localId and txHash), preventing duplicate modals.
2013
*/
21-
export const useTransactionDetailsModal = () => {
22-
const { open } = useModal();
23-
const { data: transactions } = useTransactionsHistory();
24-
25-
const openTransactionModal = useCallback((identifier: TransactionIdentifier) => {
26-
// Find the transaction to determine the best modal ID
27-
const transaction = transactions?.find(tx => {
28-
if ('txHash' in identifier && tx.txHash === identifier.txHash) return true;
29-
if ('localId' in identifier && tx.localId === identifier.localId) return true;
30-
return false;
31-
});
14+
const useTransactionDetailsModal = () => {
15+
const { mount, updateId } = useModals();
16+
const { refetch } = useTransactionsHistory();
17+
18+
const openTransactionModal = useCallback(async (tx: PartialLocalShielderActivityHistory) => {
19+
const { data } = await refetch();
20+
const thisTransaction =
21+
data?.find(transaction => transaction.txHash === tx.txHash || transaction.localId === tx.localId);
3222

33-
// Determine the modal ID priority: txHash > localId
34-
// This ensures we don't create duplicate modals when a transaction
35-
// transitions from pending (localId only) to confirmed (has txHash)
36-
const modalId = transaction?.txHash ?? ('localId' in identifier ? identifier.localId : identifier.txHash);
37-
38-
// Determine the props to pass to the modal
39-
const modalProps: TransactionIdentifier = transaction?.txHash ?
40-
{ txHash: transaction.txHash } :
41-
identifier;
42-
43-
open(
44-
<TransactionDetailsModal {...modalProps} />,
45-
{ idOverride: modalId }
46-
);
47-
}, [open, transactions]);
23+
const modalId = tx.txHash ?? tx.localId;
24+
25+
const modalProps = tx.txHash ?
26+
{ txHash: tx.txHash } :
27+
{ localId: tx.localId };
28+
29+
const updatedModal = thisTransaction?.localId && thisTransaction.txHash ?
30+
await updateId(thisTransaction.localId, thisTransaction.txHash) :
31+
null;
32+
33+
if(updatedModal) {
34+
console.warn('Transaction modal is already open, updating to new ID:', updatedModal.id);
35+
return;
36+
}
37+
38+
mount({
39+
id: modalId,
40+
modal: <TransactionDetailsModal {...modalProps} />,
41+
});
42+
}, [mount, refetch, updateId]);
4843

4944
return {
5045
openTransactionModal,

src/domains/shielder/utils/useTransactionsHistory.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ export const useTransactionsHistory = () => {
2121

2222
const upsertTransaction = useCallback(
2323
async (...params: Parameters<NonNullable<typeof db>['upsertItem']>) => {
24-
if (!db || !address || !chainId) return;
24+
if (!db || !address || !chainId) return params[1];
2525

26-
await db.upsertItem(...params);
27-
28-
await queryClient.invalidateQueries({
26+
const tx = await db.upsertItem(...params);
27+
void queryClient.invalidateQueries({
2928
queryKey: getQueryKey.shielderTransactions(address, chainId),
3029
});
30+
return tx;
3131
},
3232
[db, address, chainId, queryClient]
3333
);

src/domains/shielder/utils/useWithdraw.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import vars from 'src/domains/styling/utils/vars.ts';
1414

1515
import useShielderClient from './useShielderClient';
1616

17-
export const useWithdraw = () => {
17+
const useWithdraw = () => {
1818
const { data: shielderClient } = useShielderClient();
1919
const { address: walletAddress, chainId } = useAccount();
2020
const queryClient = useQueryClient();

0 commit comments

Comments
 (0)