diff --git a/src/components/addresses/picker/AddressPicker.tsx b/src/components/addresses/picker/AddressPicker.tsx index 62feff0..413c0aa 100644 --- a/src/components/addresses/picker/AddressPicker.tsx +++ b/src/components/addresses/picker/AddressPicker.tsx @@ -107,7 +107,6 @@ : options; // Fetch an address or name hint if possible - // TODO: Make sure these auto-refresh when the balances change const pickerHints = usePickerHints(nameHint, cleanValue, hasExactName); const classes = classNames("address-picker", className, { diff --git a/src/components/addresses/picker/PickerHints.tsx b/src/components/addresses/picker/PickerHints.tsx index 439f411..38557cc 100644 --- a/src/components/addresses/picker/PickerHints.tsx +++ b/src/components/addresses/picker/PickerHints.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { useState, useEffect, useMemo } from "react"; +import { useState, useEffect, useMemo, useRef } from "react"; import { isValidAddress, stripNameSuffix, @@ -15,6 +15,8 @@ import { AddressHint } from "./AddressHint"; import { NameHint } from "./NameHint"; +import { useSubscription } from "@global/ws/WebsocketSubscription"; + import { debounce } from "lodash-es"; import Debug from "debug"; @@ -27,6 +29,9 @@ value?: string, hasExactName?: boolean ): JSX.Element | null { + // Used for clean-up + const isMounted = useRef(true); + const addressPrefix = useAddressPrefix(); const nameSuffix = useNameSuffix(); @@ -34,36 +39,66 @@ const [foundAddress, setFoundAddress] = useState(); const [foundName, setFoundName] = useState(); - const lookupHint = useMemo(() => debounce(( + // To auto-refresh address balances, we need to subscribe to the address. + // This is the address to subscribe to: + const [validAddress, setValidAddress] = useState(); + const lastTransactionID = useSubscription({ address: validAddress }); + + // The actual lookup function (debounced) + const lookupHint = useMemo(() => debounce(async ( nameSuffix: string, value: string, hasAddress?: boolean, hasName?: boolean, nameHint?: boolean ) => { + // Skip doing anything when unmounted to avoid illegal state updates + if (!isMounted.current) return debug("unmounted skipped lookupHint"); + debug("looking up hint for %s (address: %b) (name: %b)", value, hasAddress, hasName); if (hasAddress) { // Lookup an address setFoundName(undefined); - lookupAddress(value, nameHint) - .then(setFoundAddress) - .catch(() => setFoundAddress(false)); + + try { + const address = await lookupAddress(value, nameHint); + + if (!isMounted.current) + return debug("unmounted skipped lookupHint hasAddress try"); + setFoundAddress(address); + } catch (ignored) { + if (!isMounted.current) + return debug("unmounted skipped lookupHint hasAddress catch"); + setFoundAddress(false); + } } else if (hasName) { // Lookup a name setFoundAddress(undefined); - const rawName = stripNameSuffix(nameSuffix, value); + try { + const rawName = stripNameSuffix(nameSuffix, value); + const res = await api.get<{ name: KristName }>( + "names/" + encodeURIComponent(rawName) + ); - api.get<{ name: KristName }>("names/" + encodeURIComponent(rawName)) - .then(res => setFoundName(res.name)) - .catch(() => setFoundName(false)); + if (!isMounted.current) + return debug("unmounted skipped lookupHint hasName try"); + setFoundName(res.name); + } catch (ignored) { + if (!isMounted.current) + return debug("unmounted skipped lookupHint hasName catch"); + setFoundName(false); + } } }, HINT_LOOKUP_DEBOUNCE), []); // Look up the address/name if it is valid (debounced to 250ms) useEffect(() => { + // Skip doing anything when unmounted to avoid illegal state updates + if (!isMounted.current) debug("unmounted skipped lookup useEffect"); + if (!value) { setFoundAddress(undefined); setFoundName(undefined); @@ -80,9 +115,27 @@ return; } + // Update the subscription if necessary + if (hasValidAddress && validAddress !== value) { + debug("updating valid address from %s to %s", validAddress, value); + setValidAddress(value); + } + // Perform the lookup (debounced) lookupHint(nameSuffix, value, hasValidAddress, hasExactName, nameHint); - }, [lookupHint, nameSuffix, value, addressPrefix, hasExactName, nameHint]); + }, [ + lookupHint, nameSuffix, value, addressPrefix, hasExactName, nameHint, + validAddress, lastTransactionID + ]); + + // Clean up the debounced function when unmounting + useEffect(() => { + return () => { + debug("unmounting address picker hint"); + isMounted.current = false; + lookupHint?.cancel(); + }; + }, [lookupHint]); // Return an address hint if possible if (foundAddress !== undefined) return (