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.
| Props | State |
|---|
| Owner | Parent component | The component itself |
| Mutability | Read-only inside the component | Updated via self:setState |
| Access | self.props | self.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:
Pass a plain table when the new values do not depend on the existing state:
self:setState({
isVisible = true
})
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
| Method | When 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
| Method | When 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
| Method | When 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:
| Method | Description |
|---|
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
| Method | Description |
|---|
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.