Start typing to search packages!
reactivetable
By @elentium
Roblox
MirroredReactiveTable V1
DX-oriented observable table implementation for Luau
ReactiveTable wraps normal Luau tables in a proxy that behaves like a table, but automatically broadcasts changes It is designed to reduce the boilerplate of manual update calls while keeping the code ergonomic
Features
- Table-like syntax via metatables (
t.key,t.key = value,for k, v in t do ... end) - Signals for reactivity:
Changed(fires on value updates)New(fires on key additions)Removed(fires on key removals)
- Automatic wrapping of nested tables into ReactiveTable objects
- 1-level ancestor notification (child writes bubble to direct parent only)
- Freeze controls:
- freeze the table (rejects writes)
- freeze signals (suppresses signal emissions)
- Utility methods:
Clone,Compare,Find,Sort,Insert,Remove,Clear - Supports self-referential tables during construction (no stack overflow)
Installation
Using Wally
Add ReactiveTable to your wally.toml:
[dependencies]
ReactiveTable = "elentium/reactivetable@0.1.1"
Then run:
wally install
Roblox Studio Direct Installation
- Install roblox/ReactiveTable.rbxm
- Insert it in your roblox studio project
Quick Start
local ReactiveTable = require(path.to.module) -- see Installation notes
local reactive = ReactiveTable.new({ hello = "world" })
print(reactive.hello) -- "world"
reactive:BindToKeyChanged("hello", function(newValue: any)
print(`key "hello" changed, new value: {newValue}`)
end)
reactive.hello = "goodbye" -- triggers the callback
-- listen for new keys
reactive.New:Connect(function(newKey: any, newValue: any)
print(`New key added: {newKey}, value: {newValue}`)
end)
reactive.someNewKey = 123 -- triggers New
Nested tables + ancestor bubbling (1-level)
local ReactiveTable = require(path.to.module)
local reactive = ReactiveTable.new({ a = { b = 10 } })
reactive.Changed:Connect(function(key: string, value: any)
print(`changed {key} -> {value}`)
end)
-- This fires `Changed` on the `a` table (and also on `reactive`)
reactive.a.b = 11
Signals
ReactiveTable exposes three signal objects:
reactive.Changed:Connect(function(key, value) ... end)reactive.New:Connect(function(key, value) ... end)reactive.Removed:Connect(function(key) ... end)
BindToKeyChanged(key, callback) is a convenience wrapper for Changed that filters by a specific key.
Connections returned by :Connect(...) support :Disconnect().
API Reference
ReactiveTable.new(tbl?) -> ReactiveTable<T>
Creates a ReactiveTable proxy.
tbl?is an optional source table (defaults to{}).- Nested tables inside
tblare automatically wrapped into ReactiveTable objects. - If the same raw table is wrapped more than once, the same userdata wrapper is reused (per process lifetime).
Introspection
reactive:GetSource() -> { [any]: any }reactive:GetSize() -> numberreactive:IsFrozen() -> booleanreactive:AreSignalsFrozen() -> boolean
Lifecycle
reactive:Destroy()- Disconnects all signal listeners
- Removes internal registry entries
Change broadcasting
reactive:BindToKeyChanged(key, callback) -> SignalConnectioncallback(newValue)
reactive:BroadcastChanges(changedKeys: { [any]: "Added" | "Removed" | "Changed" })- Manually emits signals for multiple keys (useful for batch updates)
Mutations
reactive:Insert(value: any, index?: number) -> ()- Array-style insertion with
table.insert - Emits
Newand bubblesChangedto direct ancestors
- Array-style insertion with
reactive:Remove(index?: number) -> any- Array-style removal with
table.remove - Emits
Removedand bubblesChangedto direct ancestors
- Array-style removal with
reactive:Find(needle: any, init?: number) -> number?- Works with primitives and ReactiveTable userdata.
reactive:Sort(comparator) -> ()- Sorts array contents via
table.sort.
- Sorts array contents via
reactive:Clear() -> ()- Clears an array-like ReactiveTable (sets numeric entries to nil)
Utility
reactive:Clone(deepClone?: boolean) -> ReactiveTable<T>deepClone == truedeep-copies nested tables (and clones nested reactive children)deepClone == nil/falsedoes a shallow copy: top-level is independent, nested tables are shared
reactive:Compare(other: any) -> boolean- Deep-compares contents
Freeze controls
reactive:Freeze() / reactive:Unfreeze()- Rejects write operations when frozen
reactive:FreezeSignals() / reactive:UnfreezeSignals()- Suppresses signal emissions for that specific object
Notes / Limitations
- Luau autocomplete for nested tables:
- Autocomplete will show table contents, but will not show ReactiveTable API for nested objects unless you annotate types (see
src/init.luauheader docs). - Ancestor notifications are limited to 1-level for performance and simplicity.
Running Tests
The project includes a large test suite at src/test/init.luau.
Requiring it in a Roblox environment runs correctness + performance checks and prints results/optionally creates a UI.
Contributing
Contributions are welcome. If you add features or fix edge cases, consider adding/adjusting tests in src/test/init.luau.
License
Apache License 2.0. See LICENSE for details.
Author
IAMNOTULTRA3 (a.k.a. Elite, Elentium)
Credits
Signal batching implementation based on stravant (Signal) and modified by IAMNOTULTRA3.
Package Details
Install command (Click to copy)
Version
0.1.1
License
Apache-2.0
Safe for commercial use
Modified files must carry a notice of changes. If the package ships a NOTICE file, its attributions must be preserved.
License identified from the packaged LICENSE file; the manifest declared none.
Automated license review — not legal advice.
