Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Loading data

Tasks usually live on a server, and the component has to get them after the page opens. Alpine understands asynchronous code: the init() method can be async, and await works inside it. While the data is on its way, the user sees a "Loading…" message.

A stand-in for the server

In this lesson's examples the real request is replaced by a function that returns a promise and answers after one second — like a slow server. In a real project fetch() takes its place.

<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
 
<script>
  function loadTasks() {
    return new Promise(resolve => {
      setTimeout(() => resolve(['Buy milk', 'Call Oleg']), 1000);
    });
  }
</script>
 
<ul x-data="{
  tasks: [],
  async init() {
    this.tasks = await loadTasks();
  }
}">
  <template x-for="task in tasks">
    <li x-text="task"></li>
  </template>
</ul>

One second after the page opens, two tasks appear. Press "Reset" in the result header to see it again.

The loading state

A loading property tells whether the request is in progress. A try...finally block makes sure it is reset in any case:

<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
 
<script>
  function loadTasks() {
    return new Promise(resolve => {
      setTimeout(() => resolve(['Buy milk', 'Call Oleg']), 1000);
    });
  }
</script>
 
<div x-data="{
  tasks: [],
  loading: true,
  async init() {
    try {
      this.tasks = await loadTasks();
    } finally {
      this.loading = false;
    }
  }
}">
  <p x-show="loading">Loading…</p>
  <ul x-show="!loading">
    <template x-for="task in tasks">
      <li x-text="task"></li>
    </template>
  </ul>
</div>

First you see "Loading…", and after a second the list replaces it.

Errors

A request can fail. The error is caught in catch and a message is shown:

<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
 
<script>
  function loadTasks() {
    return new Promise((resolve, reject) => {
      setTimeout(() => reject(new Error('Server unavailable')), 1000);
    });
  }
</script>
 
<div x-data="{ tasks: [], error: '', async init() {
  try {
    this.tasks = await loadTasks();
  } catch (e) {
    this.error = e.message;
  }
} }">
  <p x-show="error" x-text="'Error: ' + error"></p>
</div>

After a second, "Error: Server unavailable" appears.

With a real server

In a project the loading function calls the API with fetch(), as in the "Server requests: fetch" lesson of the tutorial:

// src/api.js
export async function loadTasks() {
  const response = await fetch('/api/tasks');
  if (!response.ok) throw new Error('Error ' + response.status);
  return response.json();
}

The component does not change: it still waits for a promise with await.

Common mistake

Reading data that is not there yet. Until the promise resolves, tasks is empty and selected is null. The expression x-text="selected.title" fails before the response arrives. Give initial values in x-data — an empty array, an empty string — and hide dependent blocks behind x-if until the data is there.

Thanks for reading!

Donate to the site — we work hard for you.