Resources


type RemoteDataState<T> = "loading" | ["data", T] | ["error", unknown];

export function RemoteData<T>(url: string) {
  return Resource((resource): Reactive<RemoteDataState<T>> => {
    const result = cell("loading" as RemoteDataState<T>);

    const controller = new AbortController();
    resource.on.cleanup(() => controller.abort());

    fetch(url, { signal: controller.signal })
      .then((response) => response.json() as Promise<T>)
      .then((data) => {
        result.set(["data", data]);
      })
      .catch((error) => {
        result.set(["error", error]);
      });

    return result;
  });
}

In UI Frameworks

React

import { use } from "@starbeam/react";
import { RemoteData } from "./remote-data.js";

export default function UserCard({ username }: { username: string }) {
  const user = use(
    () => RemoteData(`https://api.github.com/users/${username}`),
    [username]
  );

  switch (user.type) {
    case "loading":
      return <div>Loading...</div>;
    case "data":
      return (
        <div>
          <img src={user.data.avatar_url} />
          <h1>{user.data.name}</h1>
          <p>{user.data.bio}</p>
        </div>
      );
    case "error":
      return <div>Error: {user.error.message}</div>;
  }
}

Svelte

<script lang="typescript">
  import { use } from "@starbeam/svelte";
  import { RemoteData } from "./remote-data.js";

  export let username: string;

  $: user = use(RemoteData(`https://api.github.com/users/${username}`));
</script>

{#if user.type === "loading"}
  <div>Loading...</div>
{:else if user.type === "data"}
  <div>
    <img src={user.data.avatar_url} />
    <h1>{user.data.name}</h1>
    <p>{user.data.bio}</p>
  </div>
{:else if user.type === "error"}
  <div>Error: {user.error.message}</div>
{/if}

Vue

<script setup lang="ts">
import { use } from "@starbeam/vue";
import { RemoteData } from "./remote-data.js";

const props = defineProps<{ username: string }>();

const user = use(() => RemoteData(`https://api.github.com/users/${props.username}`));

defineExpose({ user });
</script>

<template>
  <div v-if="user.type === 'loading'">Loading...</div>
  <div v-if="user.type === 'data'">
    <img :src="user.data.avatar_url" />
    <h1>{user.data.name}</h1>
    <p>{user.data.bio}</p>
  </div>
  <div v-if="user.type === 'error'">Error: {user.error.message}</div>
</template>

Ember

import { use } from "@starbeam/ember";
import { LiterCounter } from "#reactive/liter-counter";

export default class LiterLikeButton extends Component {
  readonly liters = use(LiterCounter);

  <template>
    <button {{on "click" this.liters.increment}}>Add a liter</button>
    <p>{this.liters.description}</p>
  </template>
}