Skip to content

Commit

Permalink
feat: simplified setErrorMessage within Transaction component (#1047)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zizzamia authored Aug 14, 2024
1 parent 932d5b2 commit 00fd3af
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 99 deletions.
29 changes: 8 additions & 21 deletions src/transaction/components/TransactionProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ import {
useEffect,
useState,
} from 'react';
import type {
Address,
TransactionExecutionError,
TransactionReceipt,
} from 'viem';
import type { Address, TransactionReceipt } from 'viem';
import {
useAccount,
useConfig,
Expand All @@ -30,6 +26,7 @@ import type {
TransactionContextType,
TransactionProviderReact,
} from '../types';
import { isUserRejectedRequestError } from '../utils/isUserRejectedRequestError';

const emptyContext = {} as TransactionContextType;
export const TransactionContext =
Expand Down Expand Up @@ -74,7 +71,6 @@ export function TransactionProvider({
// Hooks that depend from Core Hooks
const { status: statusWriteContracts, writeContractsAsync } =
useWriteContracts({
setErrorMessage,
setLifeCycleStatus,
setTransactionId,
});
Expand All @@ -83,7 +79,6 @@ export function TransactionProvider({
writeContractAsync,
data: writeContractTransactionHash,
} = useWriteContract({
setErrorMessage,
setLifeCycleStatus,
setTransactionHashArray,
transactionHashArray,
Expand All @@ -100,6 +95,7 @@ export function TransactionProvider({
useEffect(() => {
// Emit Error
if (lifeCycleStatus.statusName === 'error') {
setErrorMessage(lifeCycleStatus.statusData.message);
onError?.(lifeCycleStatus.statusData);
}
// Emit State
Expand Down Expand Up @@ -145,15 +141,10 @@ export function TransactionProvider({
try {
await writeContractAsync?.(contract);
} catch (err) {
// if user rejected request
if (
(err as TransactionExecutionError)?.cause?.name ===
'UserRejectedRequestError'
) {
setErrorMessage('Request denied.');
} else {
setErrorMessage(GENERIC_ERROR_MESSAGE);
}
const errorMessage = isUserRejectedRequestError(err)
? 'Request denied.'
: GENERIC_ERROR_MESSAGE;
setErrorMessage(errorMessage);
}
}
}, [contracts, writeContractAsync]);
Expand Down Expand Up @@ -188,10 +179,7 @@ export function TransactionProvider({
setErrorMessage(GENERIC_ERROR_MESSAGE);
}
// handles user rejected request error
} else if (
(err as TransactionExecutionError)?.cause?.name ===
'UserRejectedRequestError'
) {
} else if (isUserRejectedRequestError(err)) {
setErrorMessage('Request denied.');
// handles generic error
} else {
Expand Down Expand Up @@ -230,7 +218,6 @@ export function TransactionProvider({
isToastVisible,
onSubmit: handleSubmit,
receipt,
setErrorMessage,
setIsToastVisible,
setLifeCycleStatus,
setTransactionId,
Expand Down
6 changes: 0 additions & 6 deletions src/transaction/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,3 @@ export const GENERIC_ERROR_MESSAGE = 'Something went wrong. Please try again.';
export const METHOD_NOT_SUPPORTED_ERROR_SUBSTRING =
'this request method is not supported';
export const SEND_CALLS_NOT_SUPPORTED_ERROR = 'SEND_CALLS_NOT_SUPPORTED_ERROR';
export const UNCAUGHT_WRITE_CONTRACT_ERROR_CODE =
'UNCAUGHT_WRITE_CONTRACT_ERROR';
export const UNCAUGHT_WRITE_CONTRACTS_ERROR_CODE =
'UNCAUGHT_WRITE_WRITE_CONTRACTS_ERROR';
export const WRITE_CONTRACT_ERROR_CODE = 'WRITE_CONTRACT_ERROR';
export const WRITE_CONTRACTS_ERROR_CODE = 'WRITE_CONTRACTS_ERROR';
3 changes: 2 additions & 1 deletion src/transaction/hooks/useCallsStatus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ describe('useCallsStatus', () => {
expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
statusName: 'error',
statusData: {
code: 'UNCAUGHT_CALL_STATUS_ERROR',
code: 'TmUCSh01',
error: JSON.stringify(mockError),
message: '',
},
});
});
Expand Down
8 changes: 5 additions & 3 deletions src/transaction/hooks/useCallsStatus.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { useCallsStatus as useCallsStatusWagmi } from 'wagmi/experimental';
import type { UseCallsStatusParams } from '../types';

const uncaughtErrorCode = 'UNCAUGHT_CALL_STATUS_ERROR';

export function useCallsStatus({
setLifeCycleStatus,
transactionId,
Expand All @@ -22,7 +20,11 @@ export function useCallsStatus({
} catch (err) {
setLifeCycleStatus({
statusName: 'error',
statusData: { code: uncaughtErrorCode, error: JSON.stringify(err) },
statusData: {
code: 'TmUCSh01',
error: JSON.stringify(err),
message: '',
},
});
return { status: 'error', transactionHash: undefined };
}
Expand Down
19 changes: 4 additions & 15 deletions src/transaction/hooks/useWriteContract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ type MockUseWriteContractReturn = {
};

describe('useWriteContract', () => {
const mockSetErrorMessage = vi.fn();
const mockSetLifeCycleStatus = vi.fn();
const mockSetTransactionHashArray = vi.fn();

Expand All @@ -39,7 +38,6 @@ describe('useWriteContract', () => {
} as MockUseWriteContractReturn);
const { result } = renderHook(() =>
useWriteContract({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionHashArray: mockSetTransactionHashArray,
}),
Expand All @@ -64,23 +62,18 @@ describe('useWriteContract', () => {
);
renderHook(() =>
useWriteContract({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionHashArray: mockSetTransactionHashArray,
}),
);

expect(onErrorCallback).toBeDefined();
onErrorCallback?.(genericError);

expect(mockSetErrorMessage).toHaveBeenCalledWith(
'Something went wrong. Please try again.',
);
expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
statusName: 'error',
statusData: {
code: 'WRITE_CONTRACT_ERROR',
code: 'TmUWCh01',
error: 'Something went wrong. Please try again.',
message: 'Something went wrong. Please try again.',
},
});
});
Expand All @@ -100,7 +93,6 @@ describe('useWriteContract', () => {
);
renderHook(() =>
useWriteContract({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionHashArray: mockSetTransactionHashArray,
}),
Expand All @@ -119,21 +111,18 @@ describe('useWriteContract', () => {
);
const { result } = renderHook(() =>
useWriteContract({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionHashArray: mockSetTransactionHashArray,
}),
);
expect(result.current.status).toBe('error');
expect(result.current.writeContractAsync).toBeInstanceOf(Function);
expect(mockSetErrorMessage).toHaveBeenCalledWith(
'Something went wrong. Please try again.',
);
expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
statusName: 'error',
statusData: {
code: 'UNCAUGHT_WRITE_CONTRACT_ERROR',
code: 'TmUWCh02',
error: JSON.stringify(uncaughtError),
message: 'Something went wrong. Please try again.',
},
});
});
Expand Down
31 changes: 13 additions & 18 deletions src/transaction/hooks/useWriteContract.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import type { Address, TransactionExecutionError } from 'viem';
import type { Address } from 'viem';
import { useWriteContract as useWriteContractWagmi } from 'wagmi';
import {
GENERIC_ERROR_MESSAGE,
UNCAUGHT_WRITE_CONTRACT_ERROR_CODE,
WRITE_CONTRACT_ERROR_CODE,
} from '../constants';
import { GENERIC_ERROR_MESSAGE } from '../constants';
import type { UseWriteContractParams } from '../types';
import { isUserRejectedRequestError } from '../utils/isUserRejectedRequestError';

/**
* Wagmi hook for single contract transactions.
* Supports both EOAs and Smart Wallets.
* Does not support transaction batching or paymasters.
*/
export function useWriteContract({
setErrorMessage,
setLifeCycleStatus,
setTransactionHashArray,
transactionHashArray,
Expand All @@ -22,17 +18,16 @@ export function useWriteContract({
const { status, writeContractAsync, data } = useWriteContractWagmi({
mutation: {
onError: (e) => {
if (
(e as TransactionExecutionError)?.cause?.name ===
'UserRejectedRequestError'
) {
setErrorMessage('Request denied.');
} else {
setErrorMessage(GENERIC_ERROR_MESSAGE);
}
const errorMessage = isUserRejectedRequestError(e)
? 'Request denied.'
: GENERIC_ERROR_MESSAGE;
setLifeCycleStatus({
statusName: 'error',
statusData: { code: WRITE_CONTRACT_ERROR_CODE, error: e.message },
statusData: {
code: 'TmUWCh01', // Transaction module UseWriteContract hook 01 error
error: e.message,
message: errorMessage,
},
});
},
onSuccess: (hash: Address) => {
Expand All @@ -47,11 +42,11 @@ export function useWriteContract({
setLifeCycleStatus({
statusName: 'error',
statusData: {
code: UNCAUGHT_WRITE_CONTRACT_ERROR_CODE,
code: 'TmUWCh02',
error: JSON.stringify(err),
message: GENERIC_ERROR_MESSAGE,
},
});
setErrorMessage(GENERIC_ERROR_MESSAGE);
return { status: 'error', writeContractAsync: () => {} };
}
}
53 changes: 41 additions & 12 deletions src/transaction/hooks/useWriteContracts.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { renderHook } from '@testing-library/react';
import type { TransactionExecutionError } from 'viem';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useWriteContracts as useWriteContractsWagmi } from 'wagmi/experimental';
import { useWriteContracts } from './useWriteContracts';
Expand All @@ -8,7 +9,6 @@ vi.mock('wagmi/experimental', () => ({
}));

describe('useWriteContracts', () => {
const mockSetErrorMessage = vi.fn();
const mockSetLifeCycleStatus = vi.fn();
const mockSetTransactionId = vi.fn();

Expand All @@ -30,21 +30,54 @@ describe('useWriteContracts', () => {
);
renderHook(() =>
useWriteContracts({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionId: mockSetTransactionId,
}),
);
expect(onErrorCallback).toBeDefined();
onErrorCallback?.(genericError);
expect(mockSetErrorMessage).toHaveBeenCalledWith(
'Something went wrong. Please try again.',
);
expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
statusName: 'error',
statusData: {
code: 'WRITE_CONTRACTS_ERROR',
code: 'TmUWCSh01',
error: 'Something went wrong. Please try again.',
message: 'Something went wrong. Please try again.',
},
});
});

it('should handle userRejectedRequestError', () => {
let onErrorCallback:
| ((error: TransactionExecutionError) => void)
| undefined;
(useWriteContractsWagmi as ReturnType<typeof vi.fn>).mockImplementation(
({ mutation }: UseWriteContractsConfig) => {
onErrorCallback = mutation.onError;
return {
writeContracts: vi.fn(),
status: 'error',
};
},
);
renderHook(() =>
useWriteContracts({
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionId: mockSetTransactionId,
}),
);
expect(onErrorCallback).toBeDefined();
onErrorCallback?.({
cause: {
name: 'UserRejectedRequestError',
},
message: 'Request denied.',
});
expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
statusName: 'error',
statusData: {
code: 'TmUWCSh01',
error: 'Request denied.',
message: 'Request denied.',
},
});
});
Expand All @@ -63,7 +96,6 @@ describe('useWriteContracts', () => {
);
renderHook(() =>
useWriteContracts({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionId: mockSetTransactionId,
}),
Expand All @@ -82,21 +114,18 @@ describe('useWriteContracts', () => {
);
const { result } = renderHook(() =>
useWriteContracts({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionId: mockSetTransactionId,
}),
);
expect(result.current.status).toBe('error');
expect(result.current.writeContracts).toBeInstanceOf(Function);
expect(mockSetErrorMessage).toHaveBeenCalledWith(
'Something went wrong. Please try again.',
);
expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
statusName: 'error',
statusData: {
code: 'UNCAUGHT_WRITE_WRITE_CONTRACTS_ERROR',
code: 'TmUWCSh02',
error: JSON.stringify(uncaughtError),
message: 'Something went wrong. Please try again.',
},
});
});
Expand Down
Loading

0 comments on commit 00fd3af

Please sign in to comment.