Starbeam is a library for building reactive data systems that integrate natively
with UI frameworks such as React, Vue, Svelte or Ember.
It interoperates natively with React state management patterns, Svelte stores,
the Vue composition API, and Ember’s auto-tracking system.

Starbeam Reactivity
Starbeam’s reactivity is based on a very simple, but powerful, idea:
- You mark your mutable state as reactive. Individual pieces of mutable state are called data cells.
- You use normal functions (or getters) to compute values based on your mutable state.
- You can turn a function into a formula cell to automatically cache it, and it will only recompute when the data cells it uses change.
- You use resources to compute values that require structured cleanup.
We call this collection of values the data universe. The data universe is always internally coherent. Once you mutate your state, you can call any function that depends on the mutable state, and that function will see an up-to-date version of the state.
Formulas, too, are always up to date. If you change a data cell that a formula depends on, and ask the formula for its current value, the formula will always produce a value that is up to date. You never need to worry about stale data.
The data universe becomes reactive when you plug it into your UI framework. Once you plug it into your UI framework, any changes to the data universe will be reflected in your UI automatically.
📝 Collectively, data cells and formula cells are called cells.
Data Cells and Formulas
Making It Universal
Plugging it into your UI
React
>
);
}”>
import { use } from "@starbeam/react"; import { InchCounter } from "#shared"; export function MeasureInches() { const inches = use(InchCounter); return ( <> <button onClick={inches.increment}>Increment Inches</button> <div>{inches.description}</div> </> ); }
Svelte
Vue
“>
<script> import { InchCounter } from "#shared"; export default { setup() { const inches = InchCounter(); return { inches, }; }, }; script> <template> <button v-on: click="inches.increment">Increment Inchesbutton> <div>{{ inches.description }}div> template>
Resources
So what is a resource? A resource is a reactive value, just like our InchCounter above, that requires some cleanup. When you use a resource, you link it to an owner object, and when the owner object is cleaned up, the resource will be cleaned up as well. In practice, most of the time, the owner object is a component in your UI framework.
The RemoteData Resource
In this example, we’ll create a RemoteData
resource that will fetch data from a remote server.
Note: We do not call this the
fetch
resource, because a resource represents a value not a task with a starting and stopping point. Because of this, the resource is linked, 1:1, to the owner object.
Inside of the RemoteData
function, we use the Resource
function to create a new resource. The Resource
constructor takes a function, which we call the “resource constructor”. The resource constructor returns a cell that represents its current value. When code uses the resource, its value will be the current value of the reactive value.
A resource constructor is called once, when the resource is first used. A resource constructor:
- creates internal cells to manage its state
- connects to any stateful external objects it needs to manage, such as a network connection
- describes how to disconnect from those external objects when the resource is cleaned up
- returns a cell that represents the resource’s current value
💡 A resource can use mutable state internally, and it can interact with the imperative world, but it exposes the messy outside world as a cell that can be used in the data universe like any other cell, including in other formulas and even other resources.
Using it in React
Now that we’ve defined our data universe, we want to plug it into React to create a reactive system.
[username]
);
if (user.type === “loading”) {
return
;
} else if (user.type === “error”) {
return
;
} else {
return
;
}
}”>
import { use } from "@starbeam/react"; function UserCard({ username }: { username: string }) { // when `username` changes, we clean up the old `RemoteData` resource and create a new one. const user = use( () => RemoteData(`https://api.github.com/users/${username}`), [username] ); if (user.type === "loading") { return <div>Loading...</div>; } else if (user.type === "error") { return <div>Error: {user.error.message}</div>; } else { return <div>{user.data.name}</div>; } }
In principle, we could turn RemoteData
into a React hook that abstracts the dependencies for once and for all. The useRemoteData
hook would take a URL, and whenever the URL changes, it would clean up the old resource and create a new one.
return use(() => RemoteData(url), [url]);
}”>
import { use } from "@starbeam/react"; function useRemoteData<T>(url: string) { return use(() => RemoteData(url), [url]); }
And now we can use it in our app:
;
} else if (user.type === “error”) {
return
;
} else {
return
;
}
}”>
import { useRemoteData } from "#hooks/remote-data"; function UserCard({ username }: { username: string }) { const user = useRemoteData(`https://api.github.com/users/${username}`); if (user.type === "loading") { return <div>Loading...</div>; } else if (user.type === "error") { return <div>Error: {user.error.message}</div>; } else { return <div>{user.data.name}</div>; } }
Using it in Svelte
We can plug the same RemoteData
resource into Svelte by turning it into a Svelte store.
Turn Firebase Into a Resource
Next, we’ll build a slightly more complicated example that uses Firebase. We’ll create a resource for the application, which subscribes to Firebase (and unsubscribes when the application is cleaned up). That app resource will vend Firebase documents as resources, which will be automatically updated when the document changes, and cleaned up when their owner is cleaned up.
Basically, we’re using Starbeam reactivity and ownership to manage a lot of the complexity that comes up when subscribing to Firebase documents.
const firebaseConfig = {
apiKey: “AIzaSyB-x-q-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X”,
authDomain: “my-app.firebaseapp.com”,
databaseURL: “https://my-app.firebaseio.com”,
projectId: “my-app”,
storageBucket: “my-app.appspot.com”,
messagingSenderId: “123456789”,
appId: “1: 123456789:web: 123456789”,
};
const app = initializeApp(firebaseConfig);
const database = getDatabase(app);
resource.on.cleanup(() => database.goOffline());
});
export function document(db: Database, path: string) {
return Resource((resource) => {
const firebaseDocument = db.ref(path);
const document = cell({ type: “loading” });
firebaseDocument.on(“value”, (snapshot) => {
document.set({ type: “data”, data: snapshot.val() });
});
resource.on.cleanup(() => firebaseDocument.off(“value”));
return () => document.current;
});
}”>
import { initializeApp } from "firebase/app"; import { getDatabase, type Database } from "firebase/database"; const firebaseConfig = { apiKey: "AIzaSyB-x-q-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X", authDomain: "my-app.firebaseapp.com", databaseURL: "https://my-app.firebaseio.com", projectId: "my-app", storageBucket: "my-app.appspot.com", messagingSenderId: "123456789", appId: "1: 123456789:web: 123456789", }; class Firebase { #db: Database; constructor(db: Database) { this.#db = db; } at(path: string) { return document(this.#db, path); } } // `firebase` is defined as a generic resource, which means it has properly described setup and cleanup. // // It is intended to be used as a service, which would make it a singleton *in the app*, but that means // that *appscan be cleaned up, which is very useful in testing and when rendering on the server in a // shared context. // // In short, instead of using module state as a singleton, use a service. export const firebase = Resource((resource) => { const firebaseConfig = { apiKey: "AIzaSyB-x-q-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X", authDomain: "my-app.firebaseapp.com", databaseURL: "https://my-app.firebaseio.com", projectId: "my-app", storageBucket: "my-app.appspot.com", messagingSenderId: "123456789", appId: "1: 123456789:web: 123456789", }; const app = initializeApp(firebaseConfig); const database = getDatabase(app); resource.on.cleanup(() => database.goOffline()); }); export function document(db: Database, path: string) { return Resource((resource) => { const firebaseDocument = db.ref(path); const document = cell({ type: "loading" }); firebaseDocument.on("value", (snapshot) => { document.set({ type: "data", data: snapshot.val() }); }); resource.on.cleanup(() => firebaseDocument.off("value")); return () => document.current; }); }
Using the Firebase Resource in React
Using the Firebase Resource in Svelte
export let path: string;
$: db = service(firebase); $: document = use(db.at(path));
{#if document.type === “loading”}
{:else}
{/if}”>
<script lang="typescript"> import { firebase } from "./firebase"; import { service } from "@starbeam/svelte"; export let path: string; $: db = service(firebase); $: document = use(db.at(path)); script> {#if document.type === "loading"} <div>Loading...div> {:else} <div>{document.data.name}div> {/if}
Using the Firebase Resource in Ember
{{#match this.document}}
{{:when “loading”}}
{{:when “data” as |user|}}
{{/match}}
}”>
import { service, resource } from "@starbeam/ember"; import { firebase } from "./firebase"; export default class extends Component { @service(firebase) db; @use document = resource(() => this.db.at(this.args.path)); {{#match this.document}} {{:when "loading"}}Loading...{{:when "data" as |user|}}{{user.name}}{{/match}} }
Using the Firebase Resource in Vue
“>
<script> import { service, resource } from "@starbeam/vue"; import { firebase } from "./firebase"; export default { setup() { const db = service(firebase); return { document: resource(() => db.at(this.args.path)), }; }, }; script> <template> <div v-if="document.type === 'loading'">Loading...div> <div v-else>{{ document.data.name }}div> template>
GIPHY App Key not set. Please check settings