Skip to content

Home › Concepts

Wrapping

When building complex UI components or interacting with external data sources, you often receive static tables of data, such as default configurations, theme settings, or initial properties. Wrap is a utility function that recursively iterates over a standard Luau table and converts all primitive values into individual, reactive Signals (nodes).

If an item in the table is already a node, Flux.wrap safely ignores it. If it encounters a nested table, it will recursively traverse and wrap its contents.

Basic Usage

You can use Flux.wrap to instantly convert a static dictionary into a reactive one. Keep in mind that Flux.wrap mutates the table you pass into it, physically replacing the original primitive values with nodes. It also returns that same table, so both Flux.wrap(t) and local t = Flux.wrap(t) work; the return value is the table you passed in.

luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Flux = require(ReplicatedStorage.Flux)

local playerStats = {
    health = 100,
    mana = 50,
    isAlive = true
}

-- Convert all primitive properties into reactive nodes
Flux.wrap(playerStats)

-- Now, playerStats properties must be read and written like standard Flux signals
print(playerStats.health()) -- > 100

-- Update the wrapped node
playerStats.health(90)

Once a value is wrapped it behaves like any other node, so you can drive reactive code from it. Operators and string interpolation read a node's current value automatically (see Signals):

luau
Flux(function()
    print("Health is now " .. playerStats.health())
end, true) -- the `true` makes this an Effect (see [Effects](/guide/concepts/effects))

playerStats.health(75) -- the Effect reruns: "Health is now 75"

Props

Flux.props builds on wrap for its most common call site: a reusable component merging the caller's values over a table of defaults. It takes a defaults schema and the caller's overrides, and returns a new wrapped table:

luau
local DEFAULT = {
    label = "",
    padding = 8,
    accent = Theme.accent, -- an existing node: shared by reference, intentionally
}

local function Badge(properties)
    local props = Flux.props(DEFAULT, properties)
    -- props.label, props.padding, props.accent are all nodes now

    return Flux.new "TextLabel" {
        Text = props.label,
        TextColor3 = props.accent,
    }
end

Three things distinguish it from calling Flux.wrap yourself:

  • The defaults table is a schema. Only keys declared in defaults are read from overrides; unknown keys are ignored, so callers can mix component props and raw instance properties in one table and you can hand the rest to hydration.
  • Nothing you pass in is mutated. wrap converts in place; props returns a fresh table, so a module-level DEFAULT stays plain and reusable as the schema.
  • Plain tables are deep-copied per call, defaults and overrides alike. Two instances never share state through the schema or through a reused override table, while nodes and other metatabled values (like a theme signal) still pass through by reference.

Conversion itself is identical to wrap: plain values become signals, functions become computeds, and existing nodes are left untouched.

Lifecycle

The values Flux.wrap creates are plain signals, so there is no lifecycle to manage by hand: each one lives as long as the table that holds it, and the garbage collector reclaims them once you drop that table. A Scope does not own wrapped signals: they are not torn down by scope:Destroy(); they simply go away with the table.

luau
local theme = {
    primaryColor = Color3.fromRGB(255, 0, 0),
    cornerRadius = UDim.new(0, 8),
    fonts = {
        header = Font.fromName("GothamSSm")
    }
}

Flux.wrap(theme)
-- theme.primaryColor, theme.cornerRadius, and theme.fonts.header are now signals.

-- Drop every reference to `theme` and its wrapped signals are collected: nothing to destroy by hand.

Wrap vs. Store

Because both Flux.wrap and Flux.store deal with adding reactivity to tables, it is important to understand their fundamental architectural differences:

MechanismSyntaxBest For
Flux.wrapPhysically replaces primitive values with nodes.Requires calling the node to read/write: state.health(90)Component property destructuring, converting static configurations into standalone bindings.
Flux.storeWraps the entire table in an invisible Proxy (metatable).Uses standard Luau syntax: state.health = 90Managing central application state, deeply nested dynamic data, and arrays.

The mechanism also has a performance consequence: Flux.wrap is eager (it allocates a node for every leaf the moment you call it), whereas Flux.store is lazy, creating a per-key node only the first time that key is read inside a reactive computation. For a small, fully-consumed config or prop table that cost is negligible; for large or mostly-unread data, a Store pays only for what you actually touch.

Use Flux.wrap when you are building a reusable UI component that explicitly expects its properties to be Flux nodes, and you want to ensure any static primitives passed by the developer are converted into the correct reactive format.