Reactive Containers
Efficiently manage dynamic lists of child Instances across renders.
A Container tracks child Instances by key and diffs them across renders — reusing existing children, inserting new ones, and destroying any that were not touched in the current cycle.
Register a Container
Call Handle.RegisterContainer(frame) inside a renderer or inside an Effect callback, passing the parent Instance you want to manage children inside. The Container is created once and reused on every re-render.
return Handle(function(element: Frame)
local list = Handle.RegisterContainer(element.ScrollingFrame)
end)Populate with UpsertChild
UpsertChild is the primary method for populating a Container. It parents the clone into the frame on first render and returns the already-live Instance on re-renders — so your property updates always apply to the real child.
return Handle(function(element: Frame)
local store = Fragment.useStore("Leaderboard")
local data = store.getData()
local list = Handle.RegisterContainer(element.ScrollingFrame)
for i, entry in ipairs(data.entries) do
local live = list.UpsertChild(entry.id, Assets.Row:Clone())
live.NameLabel.Text = entry.name -- always write to `live`, not the clone --
live.ScoreLabel.Text = tostring(entry.score)
live.LayoutOrder = i
end
end)Children not touched via UpsertChild or InsertChild this cycle are automatically destroyed before the next render — no manual cleanup required.
Always write to the returned value
UpsertChild may return a different Instance than the one you passed in. Always update properties on the returned live value, not on your local clone variable.
Using Components inside Containers
The recommended way to populate a Container is with Fragment.useComponent. This keeps template setup and rendering logic out of the Handle and inside a dedicated Component module.
return Handle(function(element: Frame)
local store = Fragment.useStore("Shop")
local data = store.getData()
local grid = Handle.RegisterContainer(element.ItemGrid)
for _, item in ipairs(data.items) do
local card = Fragment.useComponent("ItemCard", {
Id = item.id,
Name = item.displayName,
Price = item.price,
})
grid.UpsertChild(item.id, card)
end
end)See the Create a Component guide for how to define the ItemCard Component.
Explicit keys
When the Instance's Name is not a reliable unique key — for example when multiple children share the same name — pass an explicit key as the first argument.
local live = list.UpsertChild(entry.userId, Assets.Row:Clone())Without an explicit key the Instance's .Name property is used.
Reading children back
You can read any tracked child back from the Container at any time.
local row = list.GetChild("player_123")
if row then
row.Highlight.Visible = true
end
for key, child in pairs(list.GetAllChildren()) do
child.Active = key == selectedId
endTargeted updates with UpdateChild
UpdateChild passes the live Instance for a key to a callback. Useful for one-off mutations outside the main loop.
list.UpdateChild("player_123", function(row)
row.NameLabel.TextColor3 = Color3.new(1, 0.8, 0)
end)Removing children
Use DeleteChild to remove a single entry, or ClearChildren to wipe the entire Container.
list.DeleteChild("player_123")
list.ClearChildren()Multiple Containers in one Handle
A Handle can manage any number of Containers at once — one per parent frame.
return Handle(function(element: Frame)
local store = Fragment.useStore("Shop")
local data = store.getData()
local tabBar = Handle.RegisterContainer(element.TabBar)
local itemGrid = Handle.RegisterContainer(element.ItemGrid)
for i, tab in ipairs(shopTabs) do
local live = tabBar.UpsertChild(tab.name, Assets.Tab:Clone())
live.Active = data.activeTab == tab.name
live.LayoutOrder = i
Handle.Connect("Tab_" .. tab.name, live.Activated, function()
store.setTab(tab.name)
end)
end
for _, item in ipairs(data.items) do
local card = Fragment.useComponent("ItemCard", { id = item.id, price = item.price })
itemGrid.UpsertChild(item.id, card)
end
end)For the full Container API see the Container reference.