roact icon indicating copy to clipboard operation
roact copied to clipboard

createElement mutates props when passing in children

Open LPGhatguy opened this issue 7 years ago • 3 comments

I overlooked this when I first implemented createElement, but the passed in props table is mutated if children is also passed in:

https://github.com/Roblox/roact/blob/394e9fa918d4e0a0a7dddd344487498e9b912897/lib/Core.lua#L152-L158

This is fine if we document that Roact takes ownership of your props table when you pass it in, but I think it could potentially cause confusing behavior if a user tries to re-use props and notices children persisting (or warnings popping up).

I don't really want to copy the entire props table on every createElement since it needs to be as light and fast as possible for cheap reconciliation to be a reality.

LPGhatguy avatar Mar 04 '18 06:03 LPGhatguy

This seems like something we should document. I'm pretty sure most (all?) uses of Roact in the wild don't re-use property tables, so..

If we feel the need to not nail this down as an API dependency, one option is to use __index:

if children then
    props = setmetatable({
        [Core.Children] = children,
    }, {
        __index = props
    })
end

AmaranthineCodices avatar Mar 04 '18 14:03 AmaranthineCodices

Metatables would be a good way to prevent mutation if wanted to go down that route.

As I've thought about it, I think documentation might be the best way forward.

LPGhatguy avatar Mar 05 '18 00:03 LPGhatguy

One thought I had on this: if someone wants to re-use the props table across multiple different components, they'll get weird behavior:

local SHARED_PROPS = {
    -- ...
}

local function SomeComponent()
    return Roact.createElement("Frame", SHARED_PROPS, {
        -- some form of unique children
    })
end

local function OtherComponent()
    return Roact.createElement("Frame", SHARED_PROPS, {
        -- different children from SomeComponent
    })
end

This causes problems. I'm not sure if this is a paradigm to support, though - this example is kind of forced. This can be fixed really easily from a user point of view by copying SHARED_PROPS in each createElement call, but that's not super obvious...

I think documentation is probably the best option here; if it becomes a major problem we can move to a metatable-based proxy approach.

AmaranthineCodices avatar May 15 '18 16:05 AmaranthineCodices