Skip to content

Commit d6ace5a

Browse files
hahn-kevrmunn
andauthored
replace deriveAsync and deriveAsyncIfDefined with runed resource (#1695)
* replace deriveAsync and deriveAsyncIfDefined with runed resource * Fix type error Runed resource requires an async fetcher function, so even when we can resolve without async we need to return a resolved Promise. --------- Co-authored-by: Robin Munn <rmunn@pobox.com>
1 parent 404ae07 commit d6ace5a

5 files changed

Lines changed: 40 additions & 270 deletions

File tree

frontend/src/lib/forms/UserTypeahead.svelte

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
import { FormField, PlainInput, randomFormId } from '$lib/forms';
33
import { _userTypeaheadSearch, _usersTypeaheadSearch, type SingleUserTypeaheadResult, type SingleUserICanSeeTypeaheadResult } from '$lib/gql/typeahead-queries';
44
import { overlay } from '$lib/overlay';
5-
import { deriveAsync } from '$lib/util/time';
6-
import { writable } from 'svelte/store';
5+
import {resource} from 'runed';
76
87
type UserTypeaheadResult = SingleUserTypeaheadResult | SingleUserICanSeeTypeaheadResult;
98
let inputComponent: PlainInput | undefined = $state();
@@ -40,15 +39,14 @@
4039
4140
// making this explicit allows us to only react to input events,
4241
// rather than programmatic changes like selecting a user
43-
let trigger = writable('');
44-
const _typeaheadResults = deriveAsync(trigger, (value) => typeaheadSearch(value), [], debounceMs);
42+
let trigger = $state('');
43+
const _typeaheadResults = resource(() => trigger, (value) => typeaheadSearch(value), {initialValue: [], debounce: debounceMs});
4544
46-
// TODO: Turn this into state instead of a store at some point
47-
let selectedUser = writable<UserTypeaheadResult | null>(null);
45+
let selectedUser = $state<UserTypeaheadResult | null>(null);
4846
4947
function selectUser(user: UserTypeaheadResult): void {
50-
$selectedUser = user;
51-
onSelectedUserChange?.($selectedUser);
48+
selectedUser = user;
49+
onSelectedUserChange?.(selectedUser);
5250
selectedValue = getInputValue(user);
5351
value = selectedValue;
5452
}
@@ -99,14 +97,14 @@
9997
typeaheadResults = []; // prevent old results showing when opening next time
10098
}
10199
}
102-
let typeaheadResults = $derived($_typeaheadResults);
100+
let typeaheadResults = $derived(_typeaheadResults.current);
103101
let filteredResults = $derived(typeaheadResults.filter((user) => !exclude.includes(user.id)));
104102
// TODO: Can this be simplified by making the "value !== selectedValue" part into a $derived?
105103
// Then we'd be able to just do `$effect(() => { if (changedSelection) dispatch(...)})`
106104
// And, of course, change the dispatch into calling a prop function
107105
$effect(() => {
108-
if ($selectedUser && value !== selectedValue) {
109-
$selectedUser = null;
106+
if (selectedUser && value !== selectedValue) {
107+
selectedUser = null;
110108
selectedValue = undefined;
111109
onSelectedUserChange?.(null);
112110
}
@@ -131,7 +129,7 @@
131129
autocomplete="off"
132130
{autofocus}
133131
{keydownHandler}
134-
onInput={(value) => trigger.set(value ?? '')}
132+
onInput={(value) => trigger = value ?? ''}
135133
/>
136134
<div class="overlay-content">
137135
<ul class="menu p-0">

frontend/src/lib/util/time.test.ts

Lines changed: 0 additions & 111 deletions
This file was deleted.

frontend/src/lib/util/time.ts

Lines changed: 0 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { writable, type Readable, derived } from 'svelte/store';
2-
31
export const enum Duration {
42
Persistent = 0,
53
Default = 5000,
@@ -12,96 +10,3 @@ export async function delay<T>(ms: Duration | number = Duration.Default): Promis
1210
}
1311

1412
export const DEFAULT_DEBOUNCE_TIME = 400;
15-
16-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
17-
interface Debouncer<P extends any[]> {
18-
debounce: (...args: P) => void;
19-
debouncing: Readable<boolean>;
20-
clear: () => void;
21-
}
22-
23-
function pickDebounceTime(debounce: number | boolean): number {
24-
return typeof debounce === 'number' ? debounce : DEFAULT_DEBOUNCE_TIME;
25-
}
26-
27-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
28-
export function makeDebouncer<P extends any[]>(fn: Debouncer<P>['debounce'], debounce: number | boolean = DEFAULT_DEBOUNCE_TIME): Debouncer<P> {
29-
const debouncing = writable(false);
30-
31-
if (!debounce) {
32-
return { debounce: fn, debouncing, clear: () => { } };
33-
} else {
34-
const debounceTime = pickDebounceTime(debounce);
35-
let timeout: ReturnType<typeof setTimeout>;
36-
return {
37-
debounce: (...args: P) => {
38-
debouncing.set(true);
39-
clearTimeout(timeout);
40-
timeout = setTimeout(() => {
41-
try {
42-
fn(...args);
43-
} finally {
44-
debouncing.set(false);
45-
}
46-
}, debounceTime);
47-
},
48-
debouncing,
49-
clear: () => {
50-
clearTimeout(timeout);
51-
debouncing.set(false);
52-
},
53-
};
54-
}
55-
}
56-
57-
/**
58-
* @param fn A function that maps the store value to an async result
59-
* @returns A store that contains the result of the async function, optionally debounced
60-
*/
61-
export function deriveAsync<T, D>(
62-
store: Readable<T>,
63-
fn: (value: T) => Promise<D>,
64-
initialValue?: D,
65-
debounce: number | boolean = false): Readable<D> {
66-
67-
const debounceTime = pickDebounceTime(debounce);
68-
let timeout: ReturnType<typeof setTimeout> | undefined;
69-
70-
return derived(store, (value, set) => {
71-
clearTimeout(timeout);
72-
timeout = setTimeout(() => {
73-
const myTimeout = timeout;
74-
void fn(value).then((result) => {
75-
if (myTimeout !== timeout) return; // discard outdated results
76-
set(result);
77-
});
78-
}, debounceTime);
79-
}, initialValue);
80-
}
81-
82-
/**
83-
* @param fn A function that maps the store value to an async result, filtering out undefined values
84-
* @returns A store that contains the result of the async function
85-
*/
86-
export function deriveAsyncIfDefined<T, D>(
87-
store: Readable<T | undefined>,
88-
fn: (value: T) => Promise<D>,
89-
initialValue?: D,
90-
debounce: number | boolean = false): Readable<D> {
91-
92-
const debounceTime = pickDebounceTime(debounce);
93-
let timeout: ReturnType<typeof setTimeout> | undefined;
94-
95-
return derived(store, (value, set) => {
96-
if (value) {
97-
clearTimeout(timeout);
98-
timeout = setTimeout(() => {
99-
const myTimeout = timeout;
100-
void fn(value).then((result) => {
101-
if (myTimeout !== timeout) return; // discard outdated results
102-
set(result);
103-
});
104-
}, debounceTime);
105-
}
106-
}, initialValue);
107-
}

0 commit comments

Comments
 (0)