Skip to content

Home › Tips

Sharing State ​

In Flux, reactivity is independent of the UI tree. A Signal or Store is a standalone value. It doesn't care where it was created or which component reads it.

1. Lifting State Up (Props) ​

To share state between sibling components, create the Signal in their parent and pass it down as a prop:

luau
local function CounterDisplay(props: { count: Flux.Node<number> })
    return Flux.new "TextLabel" {
        Size = UDim2.fromOffset(200, 50),
        Text = function()
            return `Count: {props.count()}`
        end,
    }
end

local function CounterControls(props: { count: Flux.Node<number> })
    return Flux.new "TextButton" {
        Size = UDim2.fromOffset(200, 50),
        Text = "Increment",
        Activated = function()
            props.count(props.count() + 1)
        end,
    }
end

local function App()
    local sharedCount = Flux.signal(0)

    return Flux.new "Frame" {
        Size = UDim2.fromScale(1, 1),
        CounterDisplay { count = sharedCount },
        CounterControls { count = sharedCount },
    }
end

2. Ambient Subtree State (Flux.context) ​

When many components deep in a subtree need the same ambient value (such as a theme, active locale, or modal controller), use Flux.context instead of threading props through every intermediate layer:

luau
local ThemeContext = Flux.context("Dark")

ThemeContext:provide("Light", function()
    -- Any descendant created inside this block (including inside Computeds,
    -- Effects, and event handlers) reads "Light" from ThemeContext:get()
    return MainWindow()
end)

3. Global State (ModuleScripts) ​

Because Roblox caches ModuleScript return values per Luau VM, global client state is simply a ModuleScript that returns a Signal or Store:

State/PlayerSettings.luau

luau
local Flux = require(ReplicatedStorage.Flux)

return Flux.store({
    audio = {
        masterVolume = 0.5,
        musicMuted = false,
    },
    ui = {
        theme = "Dark",
    },
})

UI/MuteButton.luau

luau
local Flux = require(ReplicatedStorage.Flux)
local settings = require(ReplicatedStorage.State.PlayerSettings)

local function MuteButton()
    return Flux.new "TextButton" {
        Text = function()
            return if settings.audio.musicMuted then "Unmute" else "Mute"
        end,
        Activated = function()
            settings.audio.musicMuted = not settings.audio.musicMuted
        end,
    }
end

To sync server state into a client Store over a RemoteEvent, pass each incoming table snapshot to Flux.Store.reconcile(store, snapshot) so only changed leaves fire. For reactive signals replicated directly between the server and clients (including per-player state and client-to-server validation), use Flux.Sync.

4. Scoped Feature State (Flux.scope) ​

When state is shared across a feature (like an inventory window or minigame) that should be completely torn down when closed, create the state and UI inside a Scope:

luau
local function InventorySystem()
    local scope, ui = Flux.scope(function()
        local selectedItem = Flux.signal(nil :: string?)

        return Flux.new "Frame" {
            Size = UDim2.fromScale(1, 1),
            Sidebar { selected = selectedItem },
            ItemGrid { selected = selectedItem },
        }
    end)

    return ui, scope -- call scope:Destroy() when closing the feature
end

5. Subscribing Outside Flux (Flux.listen & Flux.on) ​

To subscribe an imperative callback outside the reactive graph with RBXScriptSignal:Connect semantics (firing on subsequent updates on its own thread via task.spawn), use Flux.listen:

luau
local disconnect = Flux.listen(settings.audio.masterVolume, function(volume)
    SoundService.RespectFilteringEnabled = true
    masterSoundGroup.Volume = volume
end)

-- Call disconnect() when you want to stop listening

For synchronous, scope-owned reactions that also run on the initial flush (or receive prevValue), wrap Flux.on in Flux.effect:

luau
local effectNode = Flux.effect(Flux.on(settings.audio.masterVolume, function(volume, prevVolume)
    masterSoundGroup.Volume = volume
end))

Summary ​

PatternBest For
PropsPassing state from a parent to direct or shallow children
Flux.contextAmbient values shared across a deep UI subtree (themes, locales)
ModuleScriptsClient-wide singletons (player settings, session data, round state)
Flux.SyncServer-to-client synced state (shared, per-player, or validated writes)
Flux.scopeMulti-component features with a bounded lifetime (windows, minigames)
Flux.listenBridging reactive updates out to external or yielding systems