Here is an elegant way of fetching data.
export function useFetch(url, options = {}) {
const data = signal(null)
const loading = signal(true)
const error = signal(null)
const { select, ...fetchOptions } = options
let controller
let active = true
const run = () => {
controller?.abort()
controller = new AbortController()
batch(() => {
loading(true)
error(null)
})
fetch(url, { ...fetchOptions, signal: controller.signal })
.then(r => {
if (!r.ok) {
throw new Error(`${r.status} ${r.statusText}`)
}
return r.json()
})
.then(json => {
if (!active) return
batch(() => {
data(select ? select(json) : json)
loading(false)
})
})
.catch(err => {
if (!active || err.name === "AbortError") return
batch(() => {
error(err.message)
loading(false)
})
})
}
run()
return {
data,
loading,
error,
refetch: run,
cancel: () => {
active = false
controller?.abort()
}
}
}
Ans its usage:
const Products = {
oninit(vnode) {
vnode.state.fetch = useFetch(
"https://dummyjson.com/products",
{
select: r => r.products
}
)
},
onremove(vnode) {
vnode.state.fetch.cancel()
},
view(vnode) {
const { data, loading, error } = vnode.state.fetch
if (loading()) return m("p", "Loading...")
if (error()) return m("p.error", error())
return m(".products",
data().map(p =>
m(".product", [
m("h3", p.title),
m("p", `$${p.price}`)
])
)
)
}
}
m.mount(document.body, Products)
You can test it here: fetchSignal
Notice what’s missing:
no lifecycle juggling
no redraw logic
no state synchronization
The UI simply reads signals.
Top comments (0)