Skip to content
On this page

Resources

A resource is a reactive computation that needs to be cleaned up when it is no longer used.

Resources are created with an owner, and whenever the owner is cleaned up, the resource is also cleaned up. This is called ownership linking.

Typically, a component in your framework will own your resources. The framework renderer will make sure that when your component is unmounted, its associated resources are cleaned up.

Example: Ticking Stopwatch

Let's illustrate the idea of a resource by creating a stopwatch that uses a setInterval to tick. By using a Starbeam resource, we can ensure that the stopwatch is stopped when the owner is cleaned up.

A resource's return value is a function that computes the value of the resource whenever its dependencies change.

demo rendering the stopwatch

Open on CodeSandbox

Lifecycle

Let's take a look at an example of a resource that receives messages on a channel, and returns a string representing the last message it received.

INFO

In this example, the channel name that we're subscribing to is dynamic, and we want to unsubscribe from the channel whenever the channel name changes, but not when we get a new message.

tsx
function ChannelResource(channelName) {
return Resource(({ on }) => {
const lastMessage = Cell(null);
 
const channel = Channel.subscribe(channelName.read());
 
channel.onMessage((message) => {
lastMessage.set(message);
});
 
on.cleanup(() => {
channel.unsubscribe();
});
 
return Formula(() => {
const prefix = `[${channelName.read()}] `;
if (lastMessage.current === null) {
return `${prefix} No messages received yet`;
} /*E1*/ else {
return `${prefix} ${lastMessage.current}`;
}
});
});
}
tsx
function ChannelResource(channelName) {
return Resource(({ on }) => {
const lastMessage = Cell(null);
 
const channel = Channel.subscribe(channelName.read());
 
channel.onMessage((message) => {
lastMessage.set(message);
});
 
on.cleanup(() => {
channel.unsubscribe();
});
 
return Formula(() => {
const prefix = `[${channelName.read()}] `;
if (lastMessage.current === null) {
return `${prefix} No messages received yet`;
} /*E1*/ else {
return `${prefix} ${lastMessage.current}`;
}
});
});
}
tsx
function ChannelResource(
channelName: Reactive<string>,
): ResourceBlueprint<string> {
return Resource(({ on }) => {
const lastMessage = Cell(null as string | null);
 
const channel = Channel.subscribe(channelName.read());
 
channel.onMessage((message) => {
lastMessage.set(message);
});
 
on.cleanup(() => {
channel.unsubscribe();
});
 
return Formula(() => {
const prefix = `[${channelName.read()}] `;
if (lastMessage.current === null) {
return `${prefix} No messages received yet`;
} /*E1*/ else {
return `${prefix} ${lastMessage.current}`;
}
});
});
}
tsx
function ChannelResource(
channelName: Reactive<string>,
): ResourceBlueprint<string> {
return Resource(({ on }) => {
const lastMessage = Cell(null as string | null);
 
const channel = Channel.subscribe(channelName.read());
 
channel.onMessage((message) => {
lastMessage.set(message);
});
 
on.cleanup(() => {
channel.unsubscribe();
});
 
return Formula(() => {
const prefix = `[${channelName.read()}] `;
if (lastMessage.current === null) {
return `${prefix} No messages received yet`;
} /*E1*/ else {
return `${prefix} ${lastMessage.current}`;
}
});
});
}

ChannelResource is a JavaScript function that takes the channel name as a reactive input and returns a resource constructor.

That resource constructor starts by subscribing to the current value of the channelName, and then telling starbeam to unsubscribe from the channel when the resource is cleaned up.

It then creates a cell that holds the last message it received on the channel, and returns a function that returns that message as a formatted string (or a helpful message if the channel hasn't received any messages yet).

At this point, let's take a look at the dependencies:

Our output depends on the channel name and the last message received on that channel. The lastMessage depends on the channel name as well, and whenever the channel name changes, the resource is cleaned up and the channel is unsubscribed.

If we receive a new message, the lastMessage cell is set to the new message. This invalidates lastMessage and therefore the output as well.

However, this does not invalidate the resource itself, so the channel subscription remains active.

On the other hand, if we change the channelName, that invalidates the ChannelResource itself.

As a result, the resource will be cleaned up and the channel unsubscribed. After that, the resource will be re-created from the new channelName, and the process will continue.

From the perspective of the creator of a resource, the resource represents a stable reactive value.

Under the Hood

Under the hood, the internal ChannelResource instance is cleaned up and recreated whenever its inputs change. However, the resource you got back when you created it remains the same.

That's what makes it possible to pass a resource to TIMELINE.render and have it continue to work even when the internal resource is torn down and recreated.

Released under the MIT license