I have a range slider input, which should trigger an async computed property to display. While it is waiting on the response, I want to display a spinner. See below what I came up with. I sense I over complicate things and sometimes both the spinner and the resulting title displays at the same time (!). Is there a better way of doing async computed props with Alpinejs?
<script src="//unpkg.com/alpinejs" defer></script>
<script>
async function getTitle(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
const data = await response.json();
await new Promise(resolve => setTimeout(resolve, 2500)) // adds some extra slowness
return data.title.toUpperCase()
}
</script>
<div x-data="{ todoId: 10, title: 'A PRELODED TODO TITLE' }">
<h3>Title formatter</h3>
<p>Grab a todo via API and uppercases title </p>
<input x-model="todoId" x-on:change="console.log('change!'); title = null" id="steps-range" type="range" min="0" max="50" step="1">
<div x-show="title !== null" x-html="title ?? (title = (await getTitle(todoId)))"></div>
<div x-show="title === null">Loading ...</div>
</div>
I have adjusted your example in order to show a different way to handle the same problem, It's simplified on the Alpine.js side and I've adjusted your function in order to assist the label problem.
<script src="//unpkg.com/alpinejs" defer></script>
<style>
.error {
color: red;
}
.success {
color: blue;
}
.loading {
color: orange;
}
</style>
<div x-data="{
todoId: 10,
title: 'A PRELODED TODO TITLE',
error: false,
loading: false,
async getTitle(id) {
this.success = true
this.error = false
this.loading = true
this.title = 'Loading ...'
await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
.then(res => res.json())
.then(async(res) => {
await new Promise(resolve => setTimeout(resolve, 100)) // adds some extra slowness
if(res.id === parseInt(this.todoId)) {
this.loading = false
this.success = true
this.title = res.title.toUpperCase()
}
return
}).catch((e) => {
this.loading = false
this.title = e.message
this.error = true
})
}
}" x-init="$watch('todoId', (newValue, oldValue) => {
newValue != oldValue ? $nextTick(async() => { getTitle(todoId) }) : null
})">
<h3>Title formatter</h3>
<p>Grab a todo via API and uppercases title</p>
<input x-model="todoId" id="steps-range" type="range" min="0" max="50" step="1" />
<div x-show="title" :class="{'error': error, 'success': !error, 'loading': loading}" x-text="title"></div>
</div>