diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..345882d --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-10-25 - Race Condition in Async Search +**Learning:** Async search functions triggered by user input can cause race conditions where a stale response overwrites a fresh one. Using a request ID counter and capturing local state is a robust fix. +**Action:** Always implement a request ID or cancellation token for typeahead search components. diff --git a/src/routes/DomainSearch.svelte b/src/routes/DomainSearch.svelte index 2cde1b3..aa0a0c9 100644 --- a/src/routes/DomainSearch.svelte +++ b/src/routes/DomainSearch.svelte @@ -15,8 +15,10 @@ let domainName: string = ''; let nameSearched: string = ''; let isLoading: boolean = false; - let debounceTimer: NodeJS.Timeout; + let debounceTimer: ReturnType; + let lastRequestId = 0; + $: domainName, debounce(); $: errors = invalid ? validator.getErrors() : []; $: invalid = domainName !== '' && !validator.validate(domainName, { raiseError: false }); $: nameSearchedLabel = nameSearched ? `${nameSearched}.${$metaNamesSdk.config.tld}` : null; @@ -36,11 +38,18 @@ return goto(url); } - nameSearched = domainName.toLocaleLowerCase(); + const requestId = ++lastRequestId; + const currentName = domainName.toLocaleLowerCase(); + nameSearched = currentName; isLoading = true; - domain = await $metaNamesSdk.domainRepository.find(domainName); + // Capture the result in a local variable to avoid race conditions + const result = await $metaNamesSdk.domainRepository.find(currentName); + // If a new request has started since this one, ignore the result + if (requestId !== lastRequestId) return; + + domain = result; isLoading = false; } @@ -55,7 +64,6 @@ class="domain-input" variant="outlined" bind:value={domainName} - on:keyup={() => debounce()} bind:invalid label="Domain name" withTrailingIcon