Skip to content

Commit

Permalink
fix: update lifecyclestatus
Browse files Browse the repository at this point in the history
  • Loading branch information
alessey committed Sep 11, 2024
1 parent 1c42f17 commit b222a27
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 169 deletions.
7 changes: 5 additions & 2 deletions src/swap/components/SwapButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import type { SwapButtonReact } from '../types';
import { useSwapContext } from './SwapProvider';

export function SwapButton({ className, disabled = false }: SwapButtonReact) {
const { address, to, from, loading, isTransactionPending, handleSubmit } =
const { address, to, from, lifeCycleStatus: { statusData }, handleSubmit } =

Check failure on line 8 in src/swap/components/SwapButton.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

src/swap/components/SwapButton.test.tsx > SwapButton > should render button with text "Swap" when not loading

TypeError: Cannot read properties of undefined (reading 'statusData') ❯ SwapButton src/swap/components/SwapButton.tsx:8:49 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 8 in src/swap/components/SwapButton.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

src/swap/components/SwapButton.test.tsx > SwapButton > should render Spinner when loading

TypeError: Cannot read properties of undefined (reading 'statusData') ❯ SwapButton src/swap/components/SwapButton.tsx:8:49 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 8 in src/swap/components/SwapButton.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

src/swap/components/SwapButton.test.tsx > SwapButton > should disable button when required fields are missing

TypeError: Cannot read properties of undefined (reading 'statusData') ❯ SwapButton src/swap/components/SwapButton.tsx:8:49 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 8 in src/swap/components/SwapButton.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

src/swap/components/SwapButton.test.tsx > SwapButton > should call handleSubmit with mockHandleSubmit when clicked

TypeError: Cannot read properties of undefined (reading 'statusData') ❯ SwapButton src/swap/components/SwapButton.tsx:8:49 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 8 in src/swap/components/SwapButton.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

src/swap/components/SwapButton.test.tsx > SwapButton > should apply additional className correctly

TypeError: Cannot read properties of undefined (reading 'statusData') ❯ SwapButton src/swap/components/SwapButton.tsx:8:49 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 8 in src/swap/components/SwapButton.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

src/swap/components/SwapButton.test.tsx > SwapButton > should render ConnectWallet if disconnected and no missing fields

TypeError: Cannot read properties of undefined (reading 'statusData') ❯ SwapButton src/swap/components/SwapButton.tsx:8:49 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22
useSwapContext();

const isLoading =
to.loading || from.loading || loading || isTransactionPending;
to.loading ||
from.loading ||
statusData.loading ||
statusData.isTransactionPending;

const isDisabled =
!from.amount ||
Expand Down
16 changes: 4 additions & 12 deletions src/swap/components/SwapMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,16 @@ export function SwapMessage({ className }: SwapMessageReact) {
address,
to,
from,
error,
loading,
isTransactionPending,
lifeCycleStatus: { statusData },
} = useSwapContext();

const isMissingRequiredFields =
!!statusData &&
'isMissingRequiredField' in statusData &&
statusData?.isMissingRequiredField;

const message = getSwapMessage({
address,
error,
error: statusData.error,

Check failure on line 16 in src/swap/components/SwapMessage.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

src/swap/components/SwapMessage.test.tsx > SwapMessage > should render message returned by getSwapMessage

TypeError: Cannot read properties of null (reading 'error') ❯ SwapMessage src/swap/components/SwapMessage.tsx:16:23 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 16 in src/swap/components/SwapMessage.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

src/swap/components/SwapMessage.test.tsx > SwapMessage > should render with error message

TypeError: Cannot read properties of null (reading 'error') ❯ SwapMessage src/swap/components/SwapMessage.tsx:16:23 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 16 in src/swap/components/SwapMessage.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

src/swap/components/SwapMessage.test.tsx > SwapMessage > should render with loading message

TypeError: Cannot read properties of null (reading 'error') ❯ SwapMessage src/swap/components/SwapMessage.tsx:16:23 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 16 in src/swap/components/SwapMessage.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

src/swap/components/SwapMessage.test.tsx > SwapMessage > should apply additional className correctly

TypeError: Cannot read properties of null (reading 'error') ❯ SwapMessage src/swap/components/SwapMessage.tsx:16:23 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22
from,
loading,
isMissingRequiredFields,
isTransactionPending,
loading: statusData.loading,
isMissingRequiredFields: statusData.isMissingRequiredField,
isTransactionPending: statusData.isTransactionPending,
to,
});

Expand Down
165 changes: 69 additions & 96 deletions src/swap/components/SwapProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { useResetInputs } from '../hooks/useResetInputs';
import type {
LifeCycleStatus,
SwapContextType,
SwapError,
SwapProviderReact,
} from '../types';
import { isSwapError } from '../utils/isSwapError';
Expand Down Expand Up @@ -46,21 +45,19 @@ export function SwapProvider({
const { address } = useAccount();
// Feature flags
const { useAggregator } = experimental;
const [maxSlippage, _setMaxSlippage] = useState(
experimental.maxSlippage || 3,
);

// Core Hooks
const config = useConfig();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<SwapError>();
const [isTransactionPending, setPendingTransaction] = useState(false);
const [lifeCycleStatus, setLifeCycleStatus] = useState<LifeCycleStatus>({
const initialLifecycleStatus = {
statusName: 'init',
statusData: {
loading: false,
isTransactionPending: false,
isMissingRequiredField: true,
maxSlippage,
},
}); // Component lifecycle
maxSlippage: experimental.maxSlippage || 3
}
} as LifeCycleStatus;
const [lifeCycleStatus, setLifeCycleStatus] = useState<LifeCycleStatus>(initialLifecycleStatus); // Component lifecycle
const [hasHandledSuccess, setHasHandledSuccess] = useState(false);
const { from, to } = useFromTo(address);
const { sendTransactionAsync } = useSendTransaction(); // Sending the transaction (and approval, if applicable)
Expand All @@ -72,26 +69,9 @@ export function SwapProvider({
useEffect(() => {
// Error
if (lifeCycleStatus.statusName === 'error') {
setLoading(false);
setPendingTransaction(false);
setError(lifeCycleStatus.statusData);
onError?.(lifeCycleStatus.statusData);
}
if (lifeCycleStatus.statusName === 'amountChange') {
setError(undefined);
}
if (lifeCycleStatus.statusName === 'transactionPending') {
setLoading(true);
setPendingTransaction(true);
}
if (lifeCycleStatus.statusName === 'transactionApproved') {
setPendingTransaction(false);
onError?.(lifeCycleStatus.statusData.error);
}
// Success
if (lifeCycleStatus.statusName === 'success') {
setError(undefined);
setLoading(false);
setPendingTransaction(false);
onSuccess?.(lifeCycleStatus.statusData.transactionReceipt);
setHasHandledSuccess(true);
}
Expand Down Expand Up @@ -119,20 +99,12 @@ export function SwapProvider({
useEffect(() => {
// Reset status to init after success has been handled
if (lifeCycleStatus.statusName === 'success' && hasHandledSuccess) {
setLifeCycleStatus({
statusName: 'init',
statusData: {
isMissingRequiredField:
lifeCycleStatus.statusData.isMissingRequiredField,
maxSlippage,
},
});
setLifeCycleStatus(initialLifecycleStatus);
}
}, [
initialLifecycleStatus,
hasHandledSuccess,
lifeCycleStatus.statusData,
lifeCycleStatus.statusName,
maxSlippage,
]);

const handleToggle = useCallback(() => {
Expand All @@ -158,18 +130,18 @@ export function SwapProvider({

// if token is missing alert user via isMissingRequiredField
if (source.token === undefined || destination.token === undefined) {
setLifeCycleStatus({
setLifeCycleStatus((status: LifeCycleStatus) => ({
statusName: 'amountChange',
statusData: {
...status.statusData,
amountFrom: from.amount,
amountTo: to.amount,
maxSlippage,
tokenFrom: from.token,
tokenTo: to.token,
// token is missing
isMissingRequiredField: true,
},
});
}));
return;
}
if (amount === '' || amount === '.' || Number.parseFloat(amount) === 0) {
Expand All @@ -179,98 +151,102 @@ export function SwapProvider({
// When toAmount changes we fetch quote for fromAmount
// so set isFromQuoteLoading to true
destination.setLoading(true);
setLifeCycleStatus({
setLifeCycleStatus((status: LifeCycleStatus) => ({
statusName: 'amountChange',
statusData: {
...status.statusData,
// when fetching quote, the previous
// amount is irrelevant
amountFrom: type === 'from' ? amount : '',
amountTo: type === 'to' ? amount : '',
// when fetching quote, the destination
// amount is missing
isMissingRequiredField: true,
maxSlippage,
tokenFrom: from.token,
tokenFrom: from.token, // are these needed???
tokenTo: to.token,
},
});
}));

try {
const response = await getSwapQuote({
amount,
amountReference: 'from',
from: source.token,
to: destination.token,
maxSlippage: maxSlippage.toString(),
maxSlippage: lifeCycleStatus.statusData.maxSlippage.toString(),
useAggregator,
});
// If request resolves to error response set the quoteError
// property of error state to the SwapError response
if (isSwapError(response)) {
setLifeCycleStatus({
setLifeCycleStatus((status: LifeCycleStatus) => ({
statusName: 'error',
statusData: {
code: response.code,
error: response.error,
message: '',
// LifecycleStatus shared data
isMissingRequiredField:
lifeCycleStatus.statusData.isMissingRequiredField,
maxSlippage,
...status.statusData,
loading: false,
isTransactionPending: false,
error: {
code: response.code,
error: response.error,
message: '',
}
},
});
}));
return;
}
const formattedAmount = formatTokenAmount(
response.toAmount,
response.to.decimals,
);
destination.setAmount(formattedAmount);
setLifeCycleStatus({
setLifeCycleStatus((status: LifeCycleStatus) => ({
statusName: 'amountChange',
statusData: {
...status.statusData,
amountFrom: type === 'from' ? amount : formattedAmount,
amountTo: type === 'to' ? amount : formattedAmount,
// if quote was fetched successfully, we
// have all required fields
isMissingRequiredField: !formattedAmount,
maxSlippage,
tokenFrom: from.token,
tokenTo: to.token,
},
});
}));
} catch (err) {
setLifeCycleStatus({
setLifeCycleStatus((status: LifeCycleStatus) => ({
statusName: 'error',
statusData: {
code: 'TmSPc01', // Transaction module SwapProvider component 01 error
error: JSON.stringify(err),
message: '',
// LifecycleStatus shared data
isMissingRequiredField:
lifeCycleStatus.statusData.isMissingRequiredField,
maxSlippage,
...status.statusData,
loading: false,
isTransactionPending: false,
error: {
code: 'TmSPc01', // Transaction module SwapProvider component 01 error
error: JSON.stringify(err),
message: '',
}
},
});
}));
} finally {
// reset loading state when quote request resolves
destination.setLoading(false);
}
},
[from, lifeCycleStatus, maxSlippage, to, useAggregator],
[from, lifeCycleStatus, to, useAggregator],
);

const handleSubmit = useCallback(async () => {
if (!address || !from.token || !to.token || !from.amount) {
return;
}
setLifeCycleStatus({
// TODO: it feels wrong to call init here, although i understand the need to set isMissingRequiredFields to false
setLifeCycleStatus((status: LifeCycleStatus) => ({
statusName: 'init',
statusData: {
...status.statusData,
// has all required fields
isMissingRequiredField: false,
maxSlippage,
},
});
}));

try {
const response = await buildSwapTransaction({
Expand All @@ -279,26 +255,26 @@ export function SwapProvider({
from: from.token,
to: to.token,
useAggregator,
maxSlippage: maxSlippage.toString(),
maxSlippage: lifeCycleStatus.statusData.maxSlippage.toString(),
});
if (isSwapError(response)) {
setLifeCycleStatus({
setLifeCycleStatus((status: LifeCycleStatus) => ({
statusName: 'error',
statusData: {
code: response.code,
error: response.error,
message: response.message,
// LifecycleStatus shared data
isMissingRequiredField:
lifeCycleStatus.statusData.isMissingRequiredField,
maxSlippage,
...status.statusData,
loading: false,
isTransactionPending: false,
error: {
code: response.code,
error: response.error,
message: response.message,
}
},
});
}));
return;
}
await processSwapTransaction({
config,
lifeCycleStatus,
sendTransactionAsync,
setLifeCycleStatus,
swapTransaction: response,
Expand All @@ -310,41 +286,38 @@ export function SwapProvider({
const errorMessage = isUserRejectedRequestError(err)
? 'Request denied.'
: GENERIC_ERROR_MESSAGE;
setLifeCycleStatus({
setLifeCycleStatus((status: LifeCycleStatus) => ({
statusName: 'error',
statusData: {
code: 'TmSPc02', // Transaction module SwapProvider component 02 error
error: JSON.stringify(err),
message: errorMessage,
// LifecycleStatus shared data
isMissingRequiredField:
lifeCycleStatus.statusData.isMissingRequiredField,
maxSlippage,
...status.statusData,
loading: false,
isTransactionPending: false,
error: {
code: 'TmSPc02', // Transaction module SwapProvider component 02 error
error: JSON.stringify(err),
message: errorMessage,
},
},
});
}));
}
}, [
address,
config,
from.amount,
from.token,
lifeCycleStatus,
maxSlippage,
sendTransactionAsync,
to.token,
useAggregator,
]);

const value = useValue({
address,
error,
from,
loading,
handleAmountChange,
handleToggle,
handleSubmit,
lifeCycleStatus,
isTransactionPending,
setLifeCycleStatus,
to,
});
Expand Down
Loading

0 comments on commit b222a27

Please sign in to comment.