From a482feb7af081c3346b8169190f9be3e0cc2ec59 Mon Sep 17 00:00:00 2001 From: "Shiv Bhonde | shivbhonde.eth" Date: Sun, 25 Feb 2024 16:48:05 +0530 Subject: [PATCH] Fix cursor stealing & display loading for AddressInput (#738) --- .../scaffold-eth/Input/AddressInput.tsx | 41 +++++++++++++++---- .../scaffold-eth/Input/InputBase.tsx | 19 ++++++++- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx b/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx index 4f057015a..164664466 100644 --- a/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx +++ b/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState } from "react"; import { blo } from "blo"; -import { useDebounce } from "usehooks-ts"; +import { useDebounceValue } from "usehooks-ts"; import { Address, isAddress } from "viem"; import { useEnsAddress, useEnsAvatar, useEnsName } from "wagmi"; import { CommonInputProps, InputBase, isENS } from "~~/components/scaffold-eth"; @@ -11,29 +11,39 @@ import { CommonInputProps, InputBase, isENS } from "~~/components/scaffold-eth"; export const AddressInput = ({ value, name, placeholder, onChange, disabled }: CommonInputProps
) => { // Debounce the input to keep clean RPC calls when resolving ENS names // If the input is an address, we don't need to debounce it - const _debouncedValue = useDebounce(value, 500); + const [_debouncedValue] = useDebounceValue(value, 500); const debouncedValue = isAddress(value) ? value : _debouncedValue; const isDebouncedValueLive = debouncedValue === value; // If the user changes the input after an ENS name is already resolved, we want to remove the stale result const settledValue = isDebouncedValueLive ? debouncedValue : undefined; - const { data: ensAddress, isLoading: isEnsAddressLoading } = useEnsAddress({ + const { + data: ensAddress, + isLoading: isEnsAddressLoading, + isError: isEnsAddressError, + isSuccess: isEnsAddressSuccess, + } = useEnsAddress({ name: settledValue, - enabled: isENS(debouncedValue), + enabled: isDebouncedValueLive && isENS(debouncedValue), chainId: 1, cacheTime: 30_000, }); const [enteredEnsName, setEnteredEnsName] = useState(); - const { data: ensName, isLoading: isEnsNameLoading } = useEnsName({ + const { + data: ensName, + isLoading: isEnsNameLoading, + isError: isEnsNameError, + isSuccess: isEnsNameSuccess, + } = useEnsName({ address: settledValue as Address, enabled: isAddress(debouncedValue), chainId: 1, cacheTime: 30_000, }); - const { data: ensAvatar } = useEnsAvatar({ + const { data: ensAvatar, isLoading: isEnsAvtarLoading } = useEnsAvatar({ name: ensName, enabled: Boolean(ensName), chainId: 1, @@ -57,6 +67,14 @@ export const AddressInput = ({ value, name, placeholder, onChange, disabled }: C [onChange], ); + const reFocus = + isEnsAddressError || + isEnsNameError || + isEnsNameSuccess || + isEnsAddressSuccess || + ensName === null || + ensAddress === null; + return ( name={name} @@ -65,9 +83,11 @@ export const AddressInput = ({ value, name, placeholder, onChange, disabled }: C value={value as Address} onChange={handleChange} disabled={isEnsAddressLoading || isEnsNameLoading || disabled} + reFocus={reFocus} prefix={ - ensName && ( + ensName ? (
+ {isEnsAvtarLoading &&
} {ensAvatar ? ( { @@ -78,6 +98,13 @@ export const AddressInput = ({ value, name, placeholder, onChange, disabled }: C ) : null} {enteredEnsName ?? ensName}
+ ) : ( + (isEnsNameLoading || isEnsAddressLoading) && ( +
+
+
+
+ ) ) } suffix={ diff --git a/packages/nextjs/components/scaffold-eth/Input/InputBase.tsx b/packages/nextjs/components/scaffold-eth/Input/InputBase.tsx index 73d5a4f8a..f38bca217 100644 --- a/packages/nextjs/components/scaffold-eth/Input/InputBase.tsx +++ b/packages/nextjs/components/scaffold-eth/Input/InputBase.tsx @@ -1,10 +1,11 @@ -import { ChangeEvent, ReactNode, useCallback } from "react"; +import { ChangeEvent, FocusEvent, ReactNode, useCallback, useEffect, useRef } from "react"; import { CommonInputProps } from "~~/components/scaffold-eth"; type InputBaseProps = CommonInputProps & { error?: boolean; prefix?: ReactNode; suffix?: ReactNode; + reFocus?: boolean; }; export const InputBase = string } | undefined = string>({ @@ -16,7 +17,10 @@ export const InputBase = string } | undefined = str disabled, prefix, suffix, + reFocus, }: InputBaseProps) => { + const inputReft = useRef(null); + let modifier = ""; if (error) { modifier = "border-error"; @@ -31,6 +35,17 @@ export const InputBase = string } | undefined = str [onChange], ); + // Runs only when reFocus prop is passed, usefull for setting the cursor + // at the end of the input. Example AddressInput + const onFocus = (e: FocusEvent) => { + if (reFocus !== undefined) { + e.currentTarget.setSelectionRange(e.currentTarget.value.length, e.currentTarget.value.length); + } + }; + useEffect(() => { + if (reFocus !== undefined && reFocus === true) inputReft.current?.focus(); + }, [reFocus]); + return (
{prefix} @@ -42,6 +57,8 @@ export const InputBase = string } | undefined = str onChange={handleChange} disabled={disabled} autoComplete="off" + ref={inputReft} + onFocus={onFocus} /> {suffix}