Skip to content

Graph ​

The reactive graph: signals hold values, computeds derive them lazily, effects react to them, and scopes own the reactions and cleanups created inside them. The documented members are flat on Flux.

luau
local count = Flux.signal(0)
local doubled = Flux.computed(function()
	return count() * 2
end)
Flux.effect(function() print(doubled()) end) -- prints 0, then 10 on the flush after `count(5)`
count(5)

Types ​

Cleanable type ​

luau
type Cleanable = (() -> ())
	| ((oldValue: any) -> ())
	| ((oldValue: any, destroyed: boolean) -> ())
	| thread
	| Instance
	| RBXScriptConnection
	| { read Destroy: (self: any) -> () }
	| { read destroy: (self: any) -> () }
	| { read Disconnect: (self: any) -> () }
	| { read disconnect: (self: any) -> () }
	| { read [number]: Cleanable }

Context type ​

luau
type Context<T> = {
	read default: T,
	--[[
	Returns the value from the innermost active `provide` call, or `default` if none is active. Computeds, effects, and
	scopes remember the context values active when they were created.
	[Open Documentation](https://flux.kohl.gg/guide/concepts/context#providing-and-reading)
	]]
	get: (self: any) -> T,
	--[[
	Runs `fn` with this context set to `value` for all code and reactive nodes created inside `fn`, then restores the
	previous value.
	[Open Documentation](https://flux.kohl.gg/guide/concepts/context#providing-and-reading)
	]]
	provide: <R>(self: any, value: T, fn: () -> R) -> R,
}

Node type ​

luau
type Node<T = any> = setmetatable<{
	--[[
	Reads the node's current value without subscribing the caller.
	[Open Documentation](https://flux.kohl.gg/guide/concepts/signals#reading-a-signal)
	]]
	read peek: (self: any) -> T,
	--[[
	Reads the node's current value (`node()` does the same) and subscribes the running
	[computed](https://flux.kohl.gg/guide/concepts/computeds) or [effect](https://flux.kohl.gg/guide/concepts/effects).
	[Open Documentation](https://flux.kohl.gg/guide/concepts/signals#reading-a-signal)
	]]
	read get: (self: any) -> T,
	--[[
	Writes `value` (`node(value)` does the same), skipping propagation when it equals the current value (`~=` or the
	node's `equals`). Pass `force = true` to notify dependents anyway.
	[Open Documentation](https://flux.kohl.gg/guide/concepts/signals#updating-a-signal)
	]]
	read set: (self: any, value: any, force: boolean?) -> T,
	--[[
	Notifies the node's dependents without changing its value, useful after mutating a table in place.
	[Open Documentation](https://flux.kohl.gg/guide/concepts/signals#forcing-an-update)
	]]
	read update: <N>(self: N) -> N,
	--[[
	Disconnects the node from its sources and observers, then runs its pending [`Flux.cleanup`](https://flux.kohl.gg/guide/concepts/effects#cleanup-with-flux-cleanup)
	values and `onDestroy` callbacks. Later reads return `nil`.
	[Open Documentation](https://flux.kohl.gg/guide/concepts/signals#destroying-a-signal)
	]]
	read Destroy: (self: any) -> (),
	--[[
	Registers `fn` to run with the node's last value when the node is destroyed. Unlike `Flux.cleanup`, `onDestroy`
	callbacks only run on final teardown and survive re-runs.
	[Open Documentation](https://flux.kohl.gg/guide/concepts/signals#destroying-a-signal)
	]]
	read onDestroy: (self: any, fn: (oldValue: T) -> ()) -> (),
}, Operators<T>>

A reactive node holding a T (a signal, computed, effect, or scope). Covariant in T so Node<"a"> is assignable to Node<string>.

State type ​

luau
type State<T = any> = setmetatable<{
	--[[
	Reads the node's current value without subscribing the caller.
	[Open Documentation](https://flux.kohl.gg/guide/concepts/signals#reading-a-signal)
	]]
	read peek: (self: any) -> T,
	--[[
	Reads the node's current value (`node()` does the same) and subscribes the running
	[computed](https://flux.kohl.gg/guide/concepts/computeds) or [effect](https://flux.kohl.gg/guide/concepts/effects).
	[Open Documentation](https://flux.kohl.gg/guide/concepts/signals#reading-a-signal)
	]]
	read get: (self: any) -> T,
	--[[
	Writes `value` (`node(value)` does the same), skipping propagation when it equals the current value (`~=` or the
	node's `equals`). Pass `force = true` to notify dependents anyway.
	[Open Documentation](https://flux.kohl.gg/guide/concepts/signals#updating-a-signal)
	]]
	read set: (self: any, value: T, force: boolean?) -> T,
	--[[
	Notifies the node's dependents without changing its value, useful after mutating a table in place.
	[Open Documentation](https://flux.kohl.gg/guide/concepts/signals#forcing-an-update)
	]]
	read update: <N>(self: N) -> N,
	--[[
	Disconnects the node from its sources and observers, then runs its pending [`Flux.cleanup`](https://flux.kohl.gg/guide/concepts/effects#cleanup-with-flux-cleanup)
	values and `onDestroy` callbacks. Later reads return `nil`.
	[Open Documentation](https://flux.kohl.gg/guide/concepts/signals#destroying-a-signal)
	]]
	read Destroy: (self: any) -> (),
	--[[
	Registers `fn` to run with the node's last value when the node is destroyed. Unlike `Flux.cleanup`, `onDestroy`
	callbacks only run on final teardown and survive re-runs.
	[Open Documentation](https://flux.kohl.gg/guide/concepts/signals#destroying-a-signal)
	]]
	read onDestroy: (self: any, fn: (oldValue: T) -> ()) -> (),
}, Operators<T>>

A writable signal node whose :set(value) method is type-checked against T. Assignable wherever Node<T> or Reactive<T> is expected.

Reactive type ​

luau
type Reactive<T> = T | Node<T> | (() -> T)

A plain T, a Node<T>, or a () -> T getter function. Read it with Flux.read or Flux.raw.

luau
type Props = { label: Flux.Reactive<string>, disabled: Flux.Reactive<boolean>? }
local function Label(props: Props) end

local function label<A>(value: A): Flux.Node<Flux.Unwrap<A>>
	return Flux.computed(function()
		return Flux.read(value)
	end)
end

Unwrap type function ​

luau
type function Unwrap(value: type)

Extracts the underlying value type T from a Node<T>, getter () -> T, or plain T (distributing over unions).

Owner type ​

luau
type Owner = { any }

Functions ​

new ​

luau
Graph.new(obj: any, isEffect: boolean?, equals: any): any

Wraps obj in a reactive node: a signal for a plain value, a computed for a function, or an effect when isEffect is true (existing nodes pass through as-is).

signal ​

luau
Graph.signal(initial: any, equals: any): any

Creates a writable signal holding initial, using equals (default ==) to skip unchanged writes. Signals aren't owned by enclosing scopes and are garbage-collected once unreferenced.

luau
local count = Flux.signal(0)
count(count() + 1)

Open Documentation

computed ​

luau
Graph.computed(fn: any, equals: any): any

Creates a lazily cached computed node derived from fn. Re-evaluates only when read after a dependency changes, and uses equals (default ==) to stop propagation when the result is unchanged.

luau
local total = Flux.computed(function()
	return price() * quantity()
end)

Open Documentation

effect ​

luau
Graph.effect(fn: (...any) -> ...any): any

Schedules fn to run on the next flush and re-run whenever any node it reads changes. Owned by the enclosing scope or stopped manually with node:Destroy().

luau
Flux.effect(function() print("count is", count()) end)

Open Documentation

getter ​

luau
Graph.getter(node: any): any

Returns a plain function that reads node with tracking, avoiding wrapper __call overhead in hot loops.

luau
local count = Flux.signal(0)
local getCount = Flux.getter(count)
Flux.effect(function()
	print(getCount())
end)

setter ​

luau
Graph.setter(node: any): any

Returns a plain function that writes node (setter(value, force)), avoiding wrapper __call overhead in hot loops.

createSignal ​

luau
Graph.createSignal<T>(initial: T, equals: ((T, T: any) -> boolean)?): (() -> T, (value: T, force: boolean?) -> T)

Creates a lightweight signal without a node wrapper and returns (getter, setter). Faster and smaller than Flux.signal, with no methods (Destroy, onObserved, etc.).

luau
local count, setCount = Flux.createSignal(0)
Flux.effect(function()
	print(count())
end)
setCount(count() + 1)

createMemo ​

luau
Graph.createMemo<T>(fn: () -> T, equals: ((T, T) -> boolean)?): () -> T

Creates a lightweight computed without a node wrapper and returns its tracked getter function. Owned by the active scope or reaction like Flux.computed.

luau
local count, setCount = Flux.createSignal(1)
local doubled = Flux.createMemo(function()
	return count() * 2
end)

createEffect ​

luau
Graph.createEffect(fn: (...any) -> ...any)

Creates a lightweight effect without returning a node wrapper. Owned by the active scope or reaction like Flux.effect.

luau
local count, setCount = Flux.createSignal(0)
Flux.createEffect(function()
	print("count is", count())
end)

flush ​

luau
Graph.flush()

Runs all queued effects immediately instead of waiting for the next frame.

Open Documentation

scope ​

luau
Graph.scope<T>(fn: (scope: Node<any>) -> T): (Node<any>, T)

Runs fn in a new owner scope and returns (scope, result). Calling scope:Destroy() tears down every computed, effect, instance, and cleanup created inside fn.

luau
local scope, frame = Flux.scope(function() return Flux.new "Frame" { Parent = gui } end)
scope:Destroy() -- destroys the frame and everything bound to it

Open Documentation

cleanup ​

luau
Graph.cleanup(obj: Cleanable)

Registers obj to be disposed via Flux.dispose before the running reaction re-runs and when its owner is destroyed. Cleanup functions receive (previousValue, isDestroyed).

luau
Flux.effect(function()
	local connection = button.Activated:Connect(onClick)
	Flux.cleanup(connection)
end)

Open Documentation

dispose ​

luau
Graph.dispose(obj: Cleanable?)

Tears down obj immediately (functions, instances, connections, threads, objects with Destroy/Disconnect methods, or arrays of cleanables). Runs untracked and warns on error instead of throwing.

luau
local connections = { part.Touched:Connect(onTouched), part.Destroying:Connect(onGone) }
Flux.dispose(connections)

Open Documentation

getOwner ​

luau
Graph.getOwner(): Owner?

Returns the active reactive owner, or nil at the root. Pass it to Flux.withOwner to attach deferred work to the same scope.

Open Documentation

withOwner ​

luau
Graph.withOwner(node: any, fn: () -> ...any): ...any

Runs fn with owner (from Flux.getOwner, a node, or nil for unowned roots) as the active owner and restores its captured context. If owner is already destroyed, anything created inside fn is disposed as soon as fn finishes.

Open Documentation

untrack ​

luau
Graph.untrack(fn: (() -> ...any)?): ...any

Runs fn without dependency tracking and returns its result. Called with no arguments (Flux.untrack()), it suspends tracking for the rest of the current reaction body or until Flux.retrack().

luau
Flux.effect(function()
	local id = selected() -- tracked
	local name = Flux.untrack(function() return names()[id] end) -- not tracked
end)

Open Documentation

retrack ​

luau
Graph.retrack()

Resumes tracking after a bare Flux.untrack() in the same function body.

luau
Flux.untrack()
local snapshot = rows()
Flux.retrack()

Open Documentation

raw ​

luau
Graph.raw(object: any): any

Reads a Reactive value (node, getter function, or plain value) without subscribing the running reaction.

Open Documentation

read ​

luau
Graph.read(object: any): any

Reads a Reactive value (node, getter function, or plain value) and subscribes the running reaction if it's dynamic.

luau
local function Label(props: { text: Flux.Reactive<string> })
	return Flux.new "TextLabel" { Text = function() return string.upper(Flux.read(props.text)) end }
end

Open Documentation

safe ​

luau
Graph.safe<T>(tryFn: () -> T, fallback: T | (err: unknown) -> T, equals: ((a: T, b: T) -> boolean)?): Node<T>

Creates a computed that evaluates tryFn and returns fallback (a static value or an untracked (err) -> T function) if tryFn throws. Automatically recovers when tracked dependencies change.

Open Documentation

context ​

luau
Graph.context(default: any): any

Creates a context with the given default value for passing data down a component tree without prop drilling.

luau
local Theme = Flux.context("dark")
Theme:provide("light", function() return App {} end) -- App and everything it builds reads "light"

Open Documentation

bindContext ​

luau
Graph.bindContext<A..., R...>(fn: (A...) -> R...): (A...) -> R...

Wraps fn so every call sees the context values active when bindContext was called. Event handlers in Flux.new/Flux.edit do this automatically.

luau
local onClick = Flux.bindContext(function()
	print(ThemeContext:get()) -- the value provided where onClick was made
end)

Open Documentation

isNode ​

luau
Graph.isNode(object: any): boolean

Returns true if object is a Flux node.

Open Documentation

isReactive ​

luau
Graph.isReactive(): boolean

Returns true if called inside a tracking computed or effect.

Node ​

Constructors: Graph.effect · Graph.scope

peek ​

luau
Node:peek(): any

Reads the node's current value without subscribing the caller.

Open Documentation

get ​

luau
Node:get(): any

Reads the node's current value (node() does the same) and subscribes the running computed or effect.

Open Documentation

set ​

luau
Node:set(value: any, force: boolean?): any

Writes value (node(value) does the same), skipping propagation when it equals the current value (~= or the node's equals). Pass force = true to notify dependents anyway.

Open Documentation

update ​

luau
Node:update(): any

Notifies the node's dependents without changing its value, useful after mutating a table in place.

Open Documentation

Destroy ​

luau
Node:Destroy()

Disconnects the node from its sources and observers, then runs its pending Flux.cleanup values and onDestroy callbacks. Later reads return nil.

Open Documentation

onDestroy ​

luau
Node:onDestroy(fn: (oldValue: any) -> ())

Registers fn to run with the node's last value when the node is destroyed. Unlike Flux.cleanup, onDestroy callbacks only run on final teardown and survive re-runs.

Open Documentation

onObserved ​

luau
Node:onObserved(fn: () -> ())

Registers fn to run when the node gains its first transitive effect observer. Pair with onUnobserved to start and stop external listeners on demand.

onUnobserved ​

luau
Node:onUnobserved(fn: () -> ())

Registers fn to run when the node loses its last transitive effect observer.

Context ​

Constructors: Graph.context

get ​

luau
Context:get<T>(): T

Returns the value from the innermost active provide call, or default if none is active. Computeds, effects, and scopes remember the context values active when they were created.

Open Documentation

provide ​

luau
Context:provide<T, R>(value: T, fn: () -> R): R

Runs fn with this context set to value for all code and reactive nodes created inside fn, then restores the previous value.

Open Documentation