@rbxts/signals
getOwner and runWithOwner
Functions for managing computation ownership in reactive contexts.
Signature
function getOwner(): ComputationNode | undefined;function runWithOwner<T>(owner: ComputationNode | undefined, fn: () => T): T;
Description
The ownership system in rbxts-signals manages the lifecycle of reactive computations. Every computation (created with functions like createEffect
or createMemo
) is an owner, and when the owner is disposed, all of its owned computations are disposed as well.
getOwner()
returns the current running computation that owns the current context, or undefined
if there is none.
runWithOwner()
runs a function with a specified owner, and then restores the previous owner after the function is executed. This is particularly useful in asynchronous contexts where the owner would otherwise be undefined.
Examples
Basic owner retrieval
// Inside a computation, the current computation is the ownercreateEffect(() => { const owner = getOwner(); print(`Effect has an owner: ${owner !== undefined}`); // Prints: "Effect has an owner: true"});
// Outside a computation, there is no ownerconst outsideOwner = getOwner();print(`Outside has an owner: ${outsideOwner !== undefined}`); // Prints: "Outside has an owner: false"
Running code with an owner
createRoot((dispose) => { const rootOwner = getOwner(); // Get the root computation node
// Later, in an asynchronous context where the owner would be lost task.spawn(() => { const asyncOwner = getOwner(); print(`Async has an owner: ${asyncOwner !== undefined}`); // Prints: "Async has an owner: false"
// Use runWithOwner to restore the owner runWithOwner(rootOwner, () => { const restoredOwner = getOwner(); print(`Restored owner: ${restoredOwner !== undefined}`); // Prints: "Restored owner: true"
// Create a new computation that will be owned by the root owner createEffect(() => { print("This effect is owned by the root"); }); }); });
return dispose;});
Handling events with proper ownership
createRoot((dispose) => { const count = createSignal(0); const owner = getOwner(); // Store the current owner
// Set up a button click handler const button = new Instance("TextButton"); button.Text = "Increment"; button.Parent = game.Workspace;
// The event callback will run outside of any reactive context button.Activated.Connect(() => { // Use runWithOwner to run with the correct ownership context runWithOwner(owner, () => { count(count() + 1); print(`Count: ${count()}`);
// Create an effect that will be properly owned createEffect(() => { print(`Effect from button click: ${count()}`); }); }); });
onCleanup(() => { button.Destroy(); });
return dispose;});
Creating disposable computations in async contexts
const createAsyncComputation = (callback: () => void): () => void => { // Get the current owner before going async const owner = getOwner();
// Return a function that creates a disposable computation return () => { return createRoot((dispose) => { runWithOwner(owner, () => { callback(); }); return dispose; }); };};
// UsagecreateEffect(() => { const message = createSignal("Hello");
// Create a disposable async computation const createDisposable = createAsyncComputation(() => { createEffect(() => { print(`Async effect: ${message()}`); }); });
// Later, create and dispose the computation const dispose = createDisposable();
// Later, dispose it task.delay(5, () => { dispose(); });});