fix: admin/logs: risk of duplicated lines
Massimo Melina committed
Feb 7, 2025 at 14:19 UTC
1b40122e1ce750cab8b7cc0ce9f299552877c63a
1 file changed
+16
-18
shared/api.ts
+16
-18
@@ -66,7 +66,8 @@ export function apiCall<T=any>(cmd: string, params?: Dict, options: ApiCallOptio
66
}).finally(() => clearTimeout(timeout)), {
67
abort() {
68
controller.abort(aborted='cancel')
69
- }
69
+ },
70
+ aborted: () => controller.signal.aborted
71
})
72
}
73
@@ -81,32 +82,29 @@ export class ApiError extends Error {
82
83
export type UseApi<T=unknown> = ReturnType<typeof useApi<T>>
84
export function useApi<T=any>(cmd: string | Falsy, params?: object, options: ApiCallOptions={}) {
84
- const [data, setData] = useStateMounted<Awaited<ReturnType<typeof apiCall<T>>> | undefined>(undefined)
85
+ const [data, setData, getData] = useStateMounted<Awaited<ReturnType<typeof apiCall<T>>> | undefined>(undefined)
86
const [error, setError] = useStateMounted<Error | undefined>(undefined)
87
const [forcer, setForcer] = useStateMounted(0)
88
const [loading, setLoading, getLoading] = useStateMounted<undefined | ReturnType<typeof apiCall>>(undefined)
89
const reloadPromise = useRef<any>()
89
- const dataRef = useRef<any>()
90
useEffect(() => {
91
setError(undefined)
92
- let aborted = false
93
- let req: undefined | ReturnType<typeof apiCall>
92
+ const aborted = () => getLoading()?.aborted()
93
const wholePromise = wait(0) // postpone a bit, so that if it is aborted immediately, it is never really fired (happens mostly in dev mode)
95
- .then(() => !cmd || aborted ? undefined : req = apiCall<T>(cmd, params, options))
96
- .then(res => aborted || setData(dataRef.current = res as any) || setError(undefined), err => {
97
- if (aborted) return
98
- setError(err)
99
- setData(dataRef.current = undefined)
94
+ .then(() => {
95
+ const ret = !cmd || aborted() ? undefined : apiCall<T>(cmd, params, options)
96
+ setLoading(ret)
97
+ return ret
98
})
99
+ .then(res => aborted() || setData(res as any) || setError(undefined),
100
+ err => {
101
+ if (aborted()) return
102
+ setError(err)
103
+ setData(undefined)
104
+ })
105
.finally(() => setLoading(reloadPromise.current = undefined))
102
- if (cmd && !aborted) setLoading(Object.assign(wholePromise, {
103
- abort() {
104
- aborted = true
105
- req?.abort()
106
- }
107
- }))
106
reloadPromise.current?.resolve(wholePromise)
109
- return () => getLoading()?.abort()
107
+ return () => { wholePromise.finally(() => getLoading()?.abort()) }
108
}, [cmd, JSON.stringify(params), forcer]) //eslint-disable-line -- json-ize to detect deep changes
109
const reload = useCallback(() => {
110
if (getLoading()) return
@@ -116,7 +114,7 @@ export function useApi<T=any>(cmd: string | Falsy, params?: object, options: Api
114
const ee = useMemo(() => new BetterEventEmitter, [])
115
const sub = useCallback((cb: Callback) => ee.on('data', cb), [ee])
116
useEffect(() => { ee.emit('data') }, [data])
119
- return { data, setData, error, reload, sub, loading, getData: () => dataRef.current }
117
+ return { data, setData, getData, error, reload, sub, loading }
118
}
119
120
type EventHandler = (type:string, data?:any) => void