Skip to content

Commit

Permalink
fix: updating widget config options in runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
chybisov committed Nov 16, 2022
1 parent b241c80 commit 75eb492
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 67 deletions.
10 changes: 7 additions & 3 deletions packages/widget/src/components/ChainSelect/useChainSelect.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import type { EVMChain } from '@lifi/sdk';
import { useFormContext } from 'react-hook-form';
import { useController, useFormContext } from 'react-hook-form';
import { useChains } from '../../hooks';
import type { SwapFormType } from '../../providers';
import { SwapFormKey, SwapFormKeyHelper } from '../../providers';
import { useChainOrder } from '../../stores';

export const useChainSelect = (formType: SwapFormType) => {
const chainKey = SwapFormKeyHelper.getChainKey(formType);
const {
field: { onChange, onBlur },
} = useController({ name: chainKey });
const { setValue } = useFormContext();
const { chains, isLoading } = useChains();
const [chainOrder, setChainOrder] = useChainOrder();
const chainKey = SwapFormKeyHelper.getChainKey(formType);

const getChains = () => {
if (!chains) {
Expand All @@ -23,7 +26,8 @@ export const useChainSelect = (formType: SwapFormType) => {
};

const setCurrentChain = (chainId: number) => {
setValue(chainKey, chainId, { shouldTouch: true });
onChange(chainId);
onBlur();
setValue(SwapFormKeyHelper.getTokenKey(formType), '');
setValue(SwapFormKeyHelper.getAmountKey(formType), '');
setValue(SwapFormKey.TokenSearchFilter, '');
Expand Down
27 changes: 27 additions & 0 deletions packages/widget/src/components/SwapInput/FitInputText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { MutableRefObject } from 'react';
import { forwardRef, useLayoutEffect } from 'react';
import { useWatch } from 'react-hook-form';
import type { SwapFormTypeProps } from '../../providers';
import { SwapFormKeyHelper } from '../../providers';
import { fitInputText } from '../../utils';
import { maxInputFontSize, minInputFontSize } from './SwapInput.style';

export const FitInputText = forwardRef<HTMLInputElement, SwapFormTypeProps>(
({ formType }, ref) => {
const amountKey = SwapFormKeyHelper.getAmountKey(formType);
const [amount] = useWatch({
name: [amountKey],
});

useLayoutEffect(() => {
fitInputText(
maxInputFontSize,
minInputFontSize,
(ref as MutableRefObject<HTMLInputElement | null>)
.current as HTMLElement,
);
}, [amount, ref]);

return null;
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import type { SwapFormTypeProps } from '../../providers';
import { SwapFormKeyHelper } from '../../providers';
import { formatTokenPrice } from '../../utils';

export const FormPriceHelperText: React.FC<
SwapFormTypeProps & { selected: boolean }
> = ({ formType, selected }) => {
export const FormPriceHelperText: React.FC<SwapFormTypeProps> = ({
formType,
}) => {
const { t } = useTranslation();
const [amount, chainId, tokenAddress] = useWatch({
name: [
Expand Down
72 changes: 25 additions & 47 deletions packages/widget/src/components/SwapInput/SwapInput.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,49 @@
import type { ChangeEvent } from 'react';
import { useLayoutEffect, useRef } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import { useRef } from 'react';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useChain, useToken } from '../../hooks';
import type { SwapFormTypeProps } from '../../providers';
import { SwapFormKeyHelper, useWidgetConfig } from '../../providers';
import { DisabledUI } from '../../types';
import { fitInputText, formatAmount } from '../../utils';
import { formatAmount } from '../../utils';
import { Card, CardTitle } from '../Card';
import { TokenAvatar, TokenAvatarSkeleton } from '../TokenAvatar';
import { FitInputText } from './FitInputText';
import { FormPriceHelperText } from './FormPriceHelperText';
import {
FormControl,
Input,
maxInputFontSize,
minInputFontSize,
} from './SwapInput.style';
import { SwapInputAdornment } from './SwapInputAdornment';
import { FormControl, Input } from './SwapInput.style';
import { SwapInputEndAdornment } from './SwapInputEndAdornment';
import { SwapInputStartAdornment } from './SwapInputStartAdornment';

export const SwapInput: React.FC<SwapFormTypeProps> = ({ formType }) => {
const { t } = useTranslation();
const { setValue } = useFormContext();
const { disabledUI } = useWidgetConfig();
const ref = useRef<HTMLInputElement>(null);

const amountKey = SwapFormKeyHelper.getAmountKey(formType);
const [amount, chainId, tokenAddress] = useWatch({
name: [
amountKey,
SwapFormKeyHelper.getChainKey(formType),
SwapFormKeyHelper.getTokenKey(formType),
],
const {
field: { onChange, onBlur, value },
fieldState: { isDirty, isTouched },
} = useController({
name: amountKey,
});
const { disabledUI } = useWidgetConfig();
const ref = useRef<HTMLInputElement>(null);

const { chain } = useChain(chainId);
const { token } = useToken(chainId, tokenAddress);
const isSelected = !!(chain && token);
console.log(isDirty, isTouched);

const handleChange = (
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { value } = event.target;
const formattedAmount = formatAmount(value, true);
setValue(amountKey, formattedAmount, {
shouldTouch: true,
});
onChange(formattedAmount);
};

const handleBlur = (
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { value } = event.target;
const formattedAmount = formatAmount(value);
setValue(amountKey, formattedAmount);
onChange(formattedAmount);
onBlur();
};

useLayoutEffect(() => {
fitInputText(
maxInputFontSize,
minInputFontSize,
ref.current as HTMLElement,
);
}, [amount]);

const disabled = disabledUI?.includes(DisabledUI.FromAmount);

return (
Expand All @@ -74,28 +55,25 @@ export const SwapInput: React.FC<SwapFormTypeProps> = ({ formType }) => {
size="small"
autoComplete="off"
placeholder="0"
startAdornment={
isSelected ? (
<TokenAvatar token={token} chain={chain} sx={{ marginLeft: 2 }} />
) : (
<TokenAvatarSkeleton sx={{ marginLeft: 2 }} />
)
}
startAdornment={<SwapInputStartAdornment formType={formType} />}
endAdornment={
!disabled ? <SwapInputAdornment formType={formType} /> : undefined
!disabled ? (
<SwapInputEndAdornment formType={formType} />
) : undefined
}
inputProps={{
inputMode: 'decimal',
}}
onChange={handleChange}
onBlur={handleBlur}
value={amount}
value={value}
name={amountKey}
disabled={disabled}
required
/>
<FormPriceHelperText selected={isSelected} formType={formType} />
<FormPriceHelperText formType={formType} />
</FormControl>
<FitInputText ref={ref} formType={formType} />
</Card>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { SwapFormTypeProps } from '../../providers';
import { SwapFormKeyHelper } from '../../providers';
import { Button } from './SwapInputAdornment.style';

export const SwapInputAdornment = ({ formType }: SwapFormTypeProps) => {
export const SwapInputEndAdornment = ({ formType }: SwapFormTypeProps) => {
const { t } = useTranslation();
const { setValue } = useFormContext();
const [chainId, tokenAddress] = useWatch({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useWatch } from 'react-hook-form';
import { useChain, useToken } from '../../hooks';
import type { SwapFormTypeProps } from '../../providers';
import { SwapFormKeyHelper } from '../../providers';
import { TokenAvatar, TokenAvatarSkeleton } from '../TokenAvatar';

export const SwapInputStartAdornment: React.FC<SwapFormTypeProps> = ({
formType,
}) => {
const [chainId, tokenAddress] = useWatch({
name: [
SwapFormKeyHelper.getChainKey(formType),
SwapFormKeyHelper.getTokenKey(formType),
],
});

const { chain } = useChain(chainId);
const { token } = useToken(chainId, tokenAddress);
const isSelected = !!(chain && token);

return isSelected ? (
<TokenAvatar token={token} chain={chain} sx={{ marginLeft: 2 }} />
) : (
<TokenAvatarSkeleton sx={{ marginLeft: 2 }} />
);
};
25 changes: 15 additions & 10 deletions packages/widget/src/components/TokenList/useTokenSelect.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import { useCallback } from 'react';
import { useFormContext } from 'react-hook-form';
import { useController, useFormContext } from 'react-hook-form';
import type { SwapFormType } from '../../providers';
import { SwapFormKeyHelper } from '../../providers';

export const useTokenSelect = (
formType: SwapFormType,
onClick?: () => void,
) => {
const tokenKey = SwapFormKeyHelper.getTokenKey(formType);
const {
field: { onChange, onBlur },
} = useController({ name: tokenKey });
const { setValue, getValues } = useFormContext();

return useCallback(
(tokenAddress: string, chainId?: number) => {
onChange(tokenAddress);
onBlur();
const selectedChainId =
chainId ?? getValues(SwapFormKeyHelper.getChainKey(formType));
setValue(SwapFormKeyHelper.getTokenKey(formType), tokenAddress, {
shouldTouch: true,
});
// Set chain again to trigger URL builder update
setValue(SwapFormKeyHelper.getChainKey(formType), selectedChainId, {
shouldTouch: true,
});
setValue(SwapFormKeyHelper.getAmountKey(formType), '');
const oppositeFormType = formType === 'from' ? 'to' : 'from';
const [selectedOppositeToken, selectedOppositeChainId] = getValues([
SwapFormKeyHelper.getTokenKey(oppositeFormType),
Expand All @@ -31,11 +29,18 @@ export const useTokenSelect = (
selectedOppositeChainId === selectedChainId
) {
setValue(SwapFormKeyHelper.getTokenKey(oppositeFormType), '', {
shouldDirty: true,
shouldTouch: true,
});
}
// Set chain again to trigger URL builder update
setValue(SwapFormKeyHelper.getChainKey(formType), selectedChainId, {
shouldDirty: true,
shouldTouch: true,
});
setValue(SwapFormKeyHelper.getAmountKey(formType), '');
onClick?.();
},
[formType, getValues, onClick, setValue],
[formType, getValues, onBlur, onChange, onClick, setValue],
);
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEffect, useMemo, useRef } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useWidgetConfig } from '../WidgetProvider';
import type { SwapFormValues } from './types';
Expand All @@ -23,8 +24,8 @@ export const SwapFormProvider: React.FC<React.PropsWithChildren<{}>> = ({
buildSwapUrl,
} = useWidgetConfig();

const methods = useForm<SwapFormValues>({
defaultValues: {
const defaultValues = useMemo(
() => ({
...formDefaultValues,
fromChain,
fromToken,
Expand All @@ -35,9 +36,29 @@ export const SwapFormProvider: React.FC<React.PropsWithChildren<{}>> = ({
toChain,
toToken,
toAddress,
},
}),
[fromAmount, fromChain, fromToken, toAddress, toChain, toToken],
);

const previousDefaultValues = useRef(defaultValues);

const methods = useForm<SwapFormValues>({
defaultValues,
});

useEffect(() => {
(Object.keys(defaultValues) as SwapFormKey[]).forEach((key) => {
if (previousDefaultValues.current[key] !== defaultValues[key]) {
methods.resetField(key, {
defaultValue: defaultValues[key] || '',
keepDirty: true,
keepTouched: true,
});
}
});
previousDefaultValues.current = defaultValues;
}, [defaultValues, methods]);

return (
<FormProvider {...methods}>
{buildSwapUrl ? <URLSearchParamsBuilder /> : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const formValueKeys = [
export const URLSearchParamsBuilder = () => {
const { pathname } = useLocation();
const {
// Have to use touchedFields, because default values are not considered dirty
touchedFields: { ...touchedFields },
} = useFormState();
const values = useWatch({ name: formValueKeys });
Expand Down

0 comments on commit 75eb492

Please sign in to comment.