Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 33 additions & 17 deletions src/Components/DynamicList.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,53 @@ local createElement = React.createElement
local useState = React.useState
local useEffect = React.useEffect
local memo = React.memo
local cloneElement = React.cloneElement

-- DynamicList reconciles a dictionary of keyed children.
-- Fixes: previous code wrapped ALL children with removal props each render causing churn & potential loops.
-- Strategy: only wrap children flagged for removal; avoid mutating props; bail out when no change.
local function DynamicList(props: { children: {} })
local children = props.children
local list, setList = useState({})

useEffect(function()
setList(function(prevState)
local state = table.clone(prevState)
local nextState = table.clone(prevState)
local changed = false

-- Add or update active children
for key, child in children do
state[key] = ReactUtil.updateReactChild(child)
end

for key, child in state do
if children[key] then
continue
local updated = ReactUtil.updateReactChild(child)
if nextState[key] ~= updated then
nextState[key] = updated
changed = true
end
end

child.props.remove = true
child.props.destroy = function()
setList(function(currentState)
local clonedState = table.clone(currentState)
clonedState[key] = nil
return clonedState
end)
-- Handle removals: mark existing entries not in children
for key, existing in nextState do
if children[key] == nil and existing ~= nil then
if not (existing.props and existing.props.remove) then
local wrapped = cloneElement(existing, {
remove = true,
destroy = function()
setList(function(currentState)
local cloned = table.clone(currentState)
cloned[key] = nil
return cloned
end)
end,
})
nextState[key] = ReactUtil.updateReactChild(wrapped)
changed = true
end
end

state[key] = ReactUtil.updateReactChild(child)
end

return state
if changed then
return nextState
end
return prevState
end)
end, { children })

Expand Down
18 changes: 14 additions & 4 deletions src/Utility/ReactUtil.lua
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
local TableUtil = require(script.Parent.TableUtil)
-- ReactUtil provides lightweight helpers for dealing with React element descriptors.
-- Deep copying a React element (as previously done) can erase Luau's refined type info
-- and produce 'unknown' for consumers. For reconciliation we only need to ensure
-- that a new table reference exists when we conceptually changed props.

local function updateReactChild(child)
local cloned = TableUtil.deepCopy(child)
cloned.type = child.type
local function updateReactChild(child: any)
-- If child is nil or not a table, just return it.
if type(child) ~= "table" then
return child
end

-- Shallow copy preserves element shape and keeps type field intact.
local cloned = {}
for k, v in child do
cloned[k] = v
end
return cloned
end

Expand Down
17 changes: 0 additions & 17 deletions src/Utility/TableUtil.lua

This file was deleted.