Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Roblox/roact/llms.txt

Use this file to discover all available pages before exploring further.

Stateful components give you two powerful tools beyond what function components provide: state, which is mutable data owned by a single component, and lifecycle methods, which let you run code at specific moments in a component’s life. Together they allow components to manage their own data, respond to the world around them, and clean up after themselves when destroyed.

Props vs State

Both props and state hold data that affects what a component renders, but they come from different sources and have different rules.
PropsState
OwnerParent componentThe component itself
MutabilityRead-only inside the componentUpdated via self:setState
Accessself.propsself.state
Props are passed down from a parent and should never be modified by the component that receives them. State is private to the component that declares it and is the correct place to put values that change over the component’s lifetime.

Setting Up Initial State

Initial state is configured inside the init lifecycle method. Call self:setState with a table of key-value pairs to establish the starting values. This call inside init is synchronous — no re-render is triggered.
function MyComponent:init()
    self:setState({
        currentTime = 0
    })
end

Updating State with setState

self:setState is the only correct way to change a component’s state. It merges the values you provide into the existing state table, leaving any keys you omit unchanged. There are two forms:

Table form

Pass a plain table when the new values do not depend on the existing state:
self:setState({
    isVisible = true
})

Function form

Pass a function when the new state depends on the previous state. Roact calls your function with (prevState, props) and uses the returned table as the partial update. Return nil to abort the update entirely, which lets Roact skip unnecessary work.
self:setState(function(state, props)
    return {
        currentTime = state.currentTime + 1
    }
end)
setState does not immediately update self.state. In some lifecycle phases Roact batches the update and applies it after the current operation completes. Never read self.state immediately after calling setState and expect to see the new value — use the function form instead when you need the latest state.
Calling setState is forbidden inside render, willUpdate, and shouldUpdate. Doing so would create a cycle — a render triggering another render — and Roact will throw an error. Calling setState inside willUnmount is silently ignored.

Roact.None

To remove a key from state entirely (rather than setting it to nil, which Lua tables cannot represent), use Roact.None as the value:
self:setState({
    temporaryValue = Roact.None
})
After this update, self.state.temporaryValue will be nil.

Lifecycle Methods

Roact calls lifecycle methods on stateful components at predictable points. You can define any subset of these methods on your component class.

Mounting

MethodWhen it runs
init(initialProps)Before the first render. Set initial state and instance variables here.
render()Required. Returns the element tree for this component. Must be a pure function of self.props and self.state.
didMount()After the component’s output has been committed to the real Roblox Instance tree. Safe to call setState here.

Updating

MethodWhen it runs
shouldUpdate(nextProps, nextState)Before re-rendering. Return false to skip the update entirely.
willUpdate(nextProps, nextState)Immediately before applying new props/state and re-rendering.
render()Re-runs whenever props or state change (unless shouldUpdate returns false).
didUpdate(previousProps, previousState)After the updated output has been committed. Safe to call setState here.

Unmounting

MethodWhen it runs
willUnmount()Just before the component and its Instances are destroyed. Use this to stop timers, cancel requests, or disconnect anything not managed by Roact.

Static Methods

These are defined on the component class table itself, not on instances:
MethodDescription
getDerivedStateFromProps(nextProps, lastState)Called before every render. Return a table to merge into state, or nil to change nothing.
validateProps(props)Called when props are set (only in debug mode). Return true on success, or false, message on failure.

Diagnostic Methods

MethodDescription
getElementTraceback()Returns the stack trace of where the element was created that this component instance’s props are based on. Intended for diagnostic tooling; returns nil if element tracebacks are not enabled in the global config.

Full Example: Ticking Clock

The example below shows a complete stateful component that uses init, render, didMount, and willUnmount together. The clock starts its own timer when mounted and cleans up when unmounted — no external Roact.update calls required.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local Roact = require(ReplicatedStorage.Roact)

local Clock = Roact.Component:extend("Clock")

function Clock:init()
    -- Use setState in init to establish initial state.
    -- This is synchronous: no re-render is triggered.
    self:setState({
        currentTime = 0
    })
end

function Clock:render()
    -- Pull state out up front as a convention.
    local currentTime = self.state.currentTime

    return Roact.createElement("ScreenGui", {}, {
        TimeLabel = Roact.createElement("TextLabel", {
            Size = UDim2.new(1, 0, 1, 0),
            Text = "Time Elapsed: " .. currentTime
        })
    })
end

-- didMount runs after the first render and its Instances are created.
function Clock:didMount()
    -- Store a flag on the instance (not in state) to control the loop.
    self.running = true

    -- Spawn a new thread so we don't block Roblox's main thread.
    spawn(function()
        while self.running do
            -- Use the function form of setState because we depend on
            -- the previous value of currentTime.
            self:setState(function(state)
                return {
                    currentTime = state.currentTime + 1
                }
            end)

            wait(1)
        end
    end)
end

-- willUnmount runs just before the component is destroyed.
function Clock:willUnmount()
    -- Setting self.running to false causes the spawned loop to exit
    -- on its next iteration.
    self.running = false
end

local PlayerGui = Players.LocalPlayer.PlayerGui

-- Mount the Clock. It manages its own timer from here on.
local handle = Roact.mount(Roact.createElement(Clock), PlayerGui, "Clock UI")

-- After 10 seconds, destroy the UI. willUnmount stops the loop cleanly.
wait(10)
Roact.unmount(handle)
Notice the pattern: init sets up state and instance variables, didMount starts side-effects that rely on the component being in the tree, and willUnmount reverses those side-effects. This trio is the backbone of most stateful Roact components. Next, learn how to wire up Roblox Instance events and property-change signals in the Events guide.

Build docs developers (and LLMs) love