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 in Roact are classes created by calling Roact.Component:extend("ClassName") (or Roact.PureComponent:extend). Each instance of such a class has access to self.props, self.state, and the methods documented on this page. Roact calls these methods at specific points in the component’s lifetime — understanding their order and restrictions is key to writing correct, performant UI.

Lifecycle Order

The following numbered sequences describe the order in which Roact calls component methods. Methods marked as static do not receive self.
1

Mounting (first render)

  1. getDerivedStateFromProps(nextProps, {}) (static, if defined)
  2. init(initialProps)
  3. getDerivedStateFromProps(nextProps, state) (static, runs again after init)
  4. render()
  5. (Roblox Instances are created)
  6. didMount()
2

Updating (props or state change)

  1. getDerivedStateFromProps(nextProps, lastState) (static, if defined)
  2. shouldUpdate(nextProps, nextState) → if false, stop here
  3. willUpdate(nextProps, nextState)
  4. render()
  5. (Roblox Instances are patched)
  6. didUpdate(previousProps, previousState)
3

Unmounting

  1. willUnmount()
  2. (children are unmounted recursively)
  3. (Roblox Instances are destroyed)

Instance Methods

init

Component:init(initialProps)
Called exactly once when a new instance of the component is first created, before the initial render. Use init to set up the initial state and to store any non-rendering values (such as refs or bindings) directly on self. You can establish initial state in two equivalent ways: calling self:setState(...) or assigning directly to self.state. Both are valid inside init — Roact treats them identically and will not schedule an extra render for either form.
Do not trigger side effects in init (network requests, event connections, Instance manipulation). Those belong in didMount, which runs after all Instances are ready.
initialProps
table
required
The props table that the component was created with, after defaultProps merging has been applied.
local MyComponent = Roact.Component:extend("MyComponent")

-- Option 1: setState inside init
function MyComponent:init(initialProps)
    self:setState({
        count = 0,
        label = initialProps.defaultLabel or "Hello",
    })

    -- Store a ref (not state) directly on self
    self.frameRef = Roact.createRef()
end

-- Option 2: direct assignment to self.state (also valid in init)
function MyComponent:init(initialProps)
    self.state = {
        count = 0,
        label = initialProps.defaultLabel or "Hello",
    }
    self.frameRef = Roact.createRef()
end

render

Component:render() -> RoactElement | nil
Describes what the component should display at the current moment. Roact calls render during the initial mount and after every accepted update. It must be defined on every component — the default implementation throws an error to prevent accidental omissions.
render must be a pure function. Its output must depend only on self.props and self.state. Do not read from external mutable state, call services, or produce side effects inside render. Roact may call render multiple times and expects consistent results.
If a component intentionally renders nothing, return nil explicitly. An implicit nil (falling off the end of the function) is also acceptable, but an explicit return nil makes the intent clear.
function MyComponent:render()
    -- Correct: depends only on props and state
    return Roact.createElement("TextLabel", {
        Text = self.props.message,
        TextColor3 = self.state.highlighted
            and Color3.fromRGB(255, 220, 0)
            or Color3.fromRGB(255, 255, 255),
    })
end

-- Correct: intentionally renders nothing
function MyComponent:render()
    return nil
end

-- WRONG: reads external mutable state
function MyComponent:render()
    return Roact.createElement("TextLabel", {
        Text = self.someExternalTable.value, -- not okay!
    })
end

setState

Component:setState(stateUpdater | stateChange) -> void
Requests an update to the component’s state. Roact may resolve the update immediately or defer it for batching. The resulting state is shallowly merged — existing state fields not mentioned in the update are preserved. Table form — pass a table whose keys are merged onto the current state:
function MyComponent:didMount()
    self:setState({
        loaded = true,
        errorMessage = nil,  -- nil has no effect; use Roact.None to delete
    })
end
Function form — pass a function that receives the current pending state and current props, and returns a partial state table. Return nil from the function to abort the update entirely (no re-render is scheduled):
function MyComponent:didMount()
    self:setState(function(prevState, props)
        if prevState.count >= props.maxCount then
            return nil  -- abort: no update needed
        end
        return { count = prevState.count + 1 }
    end)
end
Always use the function form when the new state depends on the previous state (e.g., incrementing a counter). Because setState may be asynchronous, the table form can read a stale self.state value.
To remove a key from state entirely, set it to Roact.None:
self:setState({ temporaryKey = Roact.None })
setState cannot be called from the following locations. Doing so will throw an error (or silently return, in the case of willUnmount):
  • willUnmount — the component is being torn down; state updates are meaningless.
  • willUpdate — an update is already in flight; a nested update would corrupt it.
  • render — render must be pure and side-effect-free.
  • shouldUpdate — same reason as render.
Calling setState inside init is special: the result is used as the initial state, and no additional render is scheduled.
stateUpdater
function
A function (prevState: table, props: table) -> table | nil. Return a partial state table to merge, or nil to cancel the update.
stateChange
table
A partial state table to merge onto the current state. Fields set to Roact.None are deleted.

shouldUpdate

Component:shouldUpdate(nextProps, nextState) -> bool
An optional hook that lets you short-circuit re-renders. Return false to prevent Roact from calling willUpdate, render, and didUpdate. Return true (or omit this method) to allow the update to proceed normally. The default implementation always returns true. Roact.PureComponent overrides this with a shallow equality comparison of props and state.
nextProps
table
required
The incoming props that the component would be updated with.
nextState
table
required
The incoming state that the component would be updated with.
Returns: booleantrue to proceed with the update; false to skip it.
function MyComponent:shouldUpdate(nextProps, nextState)
    -- Only re-render if the text actually changed
    return nextProps.text ~= self.props.text
        or nextState.visible ~= self.state.visible
end

getElementTraceback

Component:getElementTraceback() -> string | nil
Returns the Lua stack trace captured at the point where the element for this component was created. This is useful for building diagnostic error messages that point directly to the Roact.createElement call site.
getElementTraceback only returns a non-nil value when the elementTracing option is enabled in Roact.setGlobalConfig. Without it, the method returns nil.
function MyComponent:validateSomething()
    if not self.props.requiredValue then
        error(
            "requiredValue is missing! Created at:\n"
            .. (self:getElementTraceback() or "<enable elementTracing>")
        )
    end
end

Lifecycle Methods

didMount

Component:didMount() -> void
Fired after the component’s initial render completes and all Roblox Instances in the subtree have been created. This is the first point at which it is safe to read from the Instance hierarchy or kick off side effects. Good uses for didMount:
  • Starting network requests or data fetches
  • Connecting to Roblox events or services
  • Modifying the Roblox Instance hierarchy (e.g., re-parenting non-Roact instances)
  • Initiating animations or timers
function MyComponent:didMount()
    -- Safe to access Instances here
    local frame = self.frameRef:getValue()
    print("Frame created:", frame:GetFullName())

    -- Connect a service event
    self.heartbeatConnection = game:GetService("RunService").Heartbeat:Connect(function(dt)
        self:setState(function(prevState)
            return { elapsed = prevState.elapsed + dt }
        end)
    end)
end

willUnmount

Component:willUnmount() -> void
Fired right before Roact begins unmounting the component’s children. Use this as the component’s destructor: disconnect events, cancel pending async operations, and release any resources acquired in didMount.
setState has no effect inside willUnmount. Any state changes are silently ignored because the component will be destroyed immediately after this method returns.
function MyComponent:willUnmount()
    -- Disconnect any manually-connected events
    if self.heartbeatConnection then
        self.heartbeatConnection:Disconnect()
        self.heartbeatConnection = nil
    end
end

willUpdate

Component:willUpdate(nextProps, nextState) -> void
Fired after an update has been approved (by shouldUpdate) but before self.props and self.state are replaced. You can read the current props and state via self.props and self.state, and inspect incoming values through the arguments.
Do not call setState inside willUpdate. The component is mid-update and a nested state change will throw an error.
nextProps
table
required
The props that will be applied after this method returns.
nextState
table
required
The state that will be applied after this method returns.
function MyComponent:willUpdate(nextProps, nextState)
    if nextProps.theme ~= self.props.theme then
        print("Theme is changing from", self.props.theme, "to", nextProps.theme)
    end
end

didUpdate

Component:didUpdate(previousProps, previousState) -> void
Fired at the end of an update cycle, after self.props and self.state have been updated and all Roblox Instances have been patched. By this point, the UI reflects the new state of the component. This is the recommended place for:
  • Making network requests in response to prop changes
  • Dispatching Rodux actions based on prop/state transitions
  • Any side effect that must observe the final, committed state
Always compare the incoming previous values with the current values before triggering additional updates. Unconditionally calling setState in didUpdate will cause an infinite render loop.
previousProps
table
required
The props that were in effect before this update.
previousState
table
required
The state that was in effect before this update.
function MyComponent:didUpdate(previousProps, previousState)
    -- Only fetch when the search query actually changes
    if self.props.searchQuery ~= previousProps.searchQuery then
        self:fetchResults(self.props.searchQuery)
    end
end

Static Methods

Static methods are defined on the component class table, not on instances. They do not receive self and must be pure functions.

getDerivedStateFromProps

static Component.getDerivedStateFromProps(nextProps, lastState) -> table | nil
A static lifecycle hook that runs before every render (both initial mount and updates). It receives the incoming props and the last committed state, and should return a partial state table to merge, or nil to make no change. getDerivedStateFromProps runs before shouldUpdate. Any non-nil return value changes the state table, which means the new state will no longer be shallowly equal to the old one — this breaks PureComponent’s optimization.
This method is static — it has no access to self and cannot call instance methods. Keep it as a pure transformation from props to state.
Use getDerivedStateFromProps sparingly. In most cases, responding to prop changes in didUpdate is simpler and less error-prone. Avoid it if you are simply mirroring a prop into state as-is; just read from self.props directly in render instead.Like setState, you can return Roact.None as a field value to remove that field from the resulting state.
nextProps
table
required
The incoming props that the component is about to receive.
lastState
table
required
The most recently committed state before this update.
Returns: table | nil — a partial state table to merge, or nil to leave state unchanged.
local Slider = Roact.Component:extend("Slider")

-- Clamp the internal value to the new min/max whenever props change
function Slider.getDerivedStateFromProps(nextProps, lastState)
    local clamped = math.clamp(lastState.value, nextProps.min, nextProps.max)
    if clamped ~= lastState.value then
        return { value = clamped }
    end
    return nil
end

validateProps

static Component.validateProps(props) -> true | (false, string)
An optional static method for validating the props passed to a component. When propValidation is enabled in Roact.setGlobalConfig, Roact calls validateProps before every prop update (and before init on first mount). If validation fails, Roact throws a descriptive error. Return true to indicate the props are valid. Return false and an error message string to indicate a failure. The message string is ignored when the first return value is true.
validateProps is static — it receives only the props table and has no access to self.
Property validation is disabled by default for performance reasons. Enable it during development with:
Roact.setGlobalConfig({ propValidation = true })
Do not enable it in production environments.
props
table
required
The full props table that the component is about to receive, after defaultProps merging.
Returns: true if valid, or false, "error message" if invalid.
local Button = Roact.Component:extend("Button")

function Button.validateProps(props)
    if type(props.label) ~= "string" then
        return false, "Button requires a `label` string prop, got: " .. type(props.label)
    end
    if props.onClick ~= nil and type(props.onClick) ~= "function" then
        return false, "Button `onClick` must be a function"
    end
    return true
end

function Button:render()
    return Roact.createElement("TextButton", {
        Text = self.props.label,
        [Roact.Event.Activated] = self.props.onClick,
    })
end

Static Properties

defaultProps

static Component.defaultProps: table
A dictionary of default values for props that callers have not specified. Before init and before every update, Roact merges defaultProps with the incoming props — incoming values always take precedence. This is equivalent to a component-level fallback table.
local Badge = Roact.Component:extend("Badge")

Badge.defaultProps = {
    color = Color3.fromRGB(0, 120, 215),
    size = UDim2.new(0, 80, 0, 24),
    visible = true,
}

function Badge:render()
    -- self.props.color, size, and visible are guaranteed to exist
    return Roact.createElement("Frame", {
        Size = self.props.size,
        BackgroundColor3 = self.props.color,
        Visible = self.props.visible,
    })
end

Build docs developers (and LLMs) love