Roact Documentation

repository·master·Indexed 20 days ago

https://github.com/roblox/roact

A declarative UI library for Roblox Lua inspired by React. Roact allows developers to describe UIs as a tree of virtual elements that synchronize with real Roblox instances. Key features include the Context API for dependency injection, Bindings for high-frequency property updates, Refs for accessing Roblox Instance objects, Fragments to avoid unnecessary nesting, and Portals for rendering into separate non-Roact instances.

Tokens
12.1K
Snippets
49
Records
59
Agent score
68%

What's inside Roact

  1. What are Roact Portals and when to use them

    master

    Portals are a special type of component that allow you to render Roact-managed objects into a separate, non-Roact Instance (a target).

    This is useful for rendering UI elements that need to exist outside of the current Roact tree hierarchy, such as full-screen modal dialogs that should reside in PlayerGui even if the component triggering them is deep within a different UI hierarchy.

    Warning: Portals should only be used to target objects that are not already managed by Roact. Targeting a Roact-managed Instance with a Portal can lead to unexpected behavior or conflicts in instance management.

    -- Example of a Portal targeting Workspace
    local function PartInWorkspace(props)
    	return Roact.createElement(Roact.Portal, {
    		target = Workspace
    	}, {
    		SomePart = Roact.createElement("Part", {
    			Anchored = true
    		})
    	})
    end
  2. When to use Bindings vs Refs

    master

    Use Bindings when you need to manage specific, high-frequency, or external property values (like a number or string) without triggering a full component reconciliation.

    Use Refs when you need to access the actual Roblox Instance object itself to call its methods (like :CaptureFocus()) or to pass the Instance as a property to another object (like NextSelectionLeft).

  3. Access Roblox Instances using Refs

    master

    Refs are a special type of binding that point to Roblox Instance objects created by Roact. They allow you to access the underlying Instance and call its methods directly.

    Note: Refs can only be attached to host components (e.g., TextBox, Frame) using the Roact.Ref key. They cannot be used on custom Roact components unless forwarded.

    To use a ref:

    1. Create a ref using Roact.createRef() in init().
    2. Assign it to a host component using the [Roact.Ref] key in render().
    3. Retrieve the Instance using the .getValue() method after the component has mounted (e.g., in didMount).
    local Foo = Roact.Component:extend("Foo")
    
    function Foo:init()
    	self.textBoxRef = Roact.createRef()
    end
    
    function Foo:render()
    	return Roact.createElement("TextBox", {
    		[Roact.Ref] = self.textBoxRef,
    	})
    end
    
    function Foo:didMount()
    	-- Retrieve the actual Instance
    	local textBox = self.textBoxRef:getValue()
    	print("TextBox has this text:", textBox.Text)
    end
  4. Use the Context API for dependency injection and theming

    master

    Roact's Context API (added in v1.3.0) allows you to pass values down the component tree without manually passing them through every level of props. This is ideal for dependency injection, dynamic theming, and scoped state storage.

    To use Context, you follow a three-step pattern:

    1. Create the Context: Use Roact.createContext(defaultValue) to initialize a context object.
    2. Provide the Value: Use the Provider component from the context object. Pass the data you want to share via the value prop. Any descendant of this Provider can access this value.
    3. Consume the Value: Use the Consumer component from the context object. Pass a render function as its only prop. This function receives the current context value as its argument and returns the UI to be rendered.

    When the Provider receives a new value prop, all attached Consumer components will automatically re-render with the updated value.

    local ThemeContext = Roact.createContext({ foreground = Color3.new(1,1,1), background = Color3.new(0,0,0) })
    
    -- Provider usage
    Roact.createElement(ThemeContext.Provider, {
        value = currentTheme,
    }, children)
    
    -- Consumer usage
    Roact.createElement(ThemeContext.Consumer, {
        render = function(theme)
            return Roact.createElement("TextButton", { TextColor3 = theme.foreground })
        end
    })
  5. Manage component state in stateful components

    master

    State refers to values owned by a component. Unlike props, which are passed from a parent, state is created and managed internally by the component.

    Initializing State

    Use the init method to set up the initial state of a stateful component using self:setState().

    Updating State

    Use self:setState() to update values. setState performs a shallow merge, overwriting specified keys and leaving others untouched.

    There are two ways to call setState:

    1. Object form: Pass a table of new values to merge into the current state.
    2. Function form: Pass a function that receives the current state as an argument and returns a new state table. This is the preferred method when the new state depends on the previous state (e.g., incrementing a counter). Returning nil from this function will abort the update.
    -- Initializing state
    function MyComponent:init()
    	self:setState({
    		currentTime = 0
    	})
    end
    
    -- Updating state based on previous state
    function MyComponent:didMount()
    	self:setState(function(state)
    		return {
    			currentTime = 1 + state.currentTime
    		}
    	end)
    end
  6. Improve reconciliation efficiency with stable keys

    master

    When rendering lists of elements, avoid using array indexes as keys. If an item is inserted at the beginning of a list, using indexes causes Roact to re-render every subsequent item because their index (and thus their key) has changed.

    Instead, use stable, unique keys (such as an ID from your data) for each element in the list. This allows Roact to recognize that existing elements have simply moved rather than changed, significantly reducing the work required to update the Roblox UI.

    for i, item in ipairs(items) do
    	-- Use the item's unique ID as the key instead of the index 'i'
    	itemList[item.id] = Roact.createElement(Item, {
    		layoutOrder = i,
    		icon = item.icon,
    	})
    end
  7. Create Host Components

    master

    A host component is a string representing a Roblox class name (e.g., "Frame", "ImageButton"). When used with Roact.createElement, the props passed to the component are applied directly as properties to the resulting Roblox Instance.

    -- Example of a host component
    local element = Roact.createElement("TextLabel", {
        Text = "Hello World"
    })
  8. Create stateful components with Roact.Component

    master

    To create a stateful component, extend Roact.Component using Roact.Component:extend("ComponentName").

    Lifecycle Methods

    • init(initialProps): Called once when the component is created. Use it to set up initial state via self:setState() or by assigning directly to self.state.
    • render(): A pure function that returns an Element or nil. It must depend only on props and state. If a component renders nothing, it should explicitly return nil.
    • shouldUpdate(nextProps, nextState): Allows overriding re-rendering heuristics. Returning false prevents a re-render.
    • validateProps(props): (Optional) A static method to verify props. Returns true if valid, or false, "error message" if invalid. Note: This is disabled by default for performance and must be enabled via Roact.setGlobalConfig({ propValidation = true }).
  9. Use lifecycle methods for side-effects

    master

    Stateful components can implement lifecycle methods to respond to changes in their existence or state. These methods are ideal for side-effects like network requests, measuring UI via refs, or managing external loops.

    Key lifecycle methods include:

    • init: Used to initialize the component's state.
    • didMount: Called after the component is created and mounted. Useful for starting loops or network requests.
    • didUpdate: Called when the component's props or state change.
    • willUnmount: Called immediately before the component is destroyed. Use this to clean up side-effects, such as stopping loops or disconnecting events.
  10. Use Roact.createFragment to avoid unnecessary nesting

    master

    By default, Roact components must return a single element via Roact.createElement. This often forces developers to wrap collections of elements in a container (like a Frame), which can break layout logic (e.g., UIListLayout not applying to children because they are nested inside an extra container).

    Roact.createFragment allows a component to return a collection of elements that will be rendered as direct children of the component's parent, without adding an extra node to the Roblox hierarchy.

    local function TeamLabels(props)
    	return Roact.createFragment({
    		RedTeam = Roact.createElement("TextLabel", {
    			-- Props for item...
    		}),
    		BlueTeam = Roact.createElement("TextLabel", {
    			-- Props for item...
    		})
    	})
    end
  11. Use Bindings to manage specific Instance properties

    master

    Bindings are special objects that Roact automatically unwraps into values. When a binding is updated, Roact only updates the specific properties subscribed to it, bypassing the full reconciliation process. This is useful for high-frequency updates or properties that are difficult to manage via standard state, such as animations or dynamic resizing.

    To use bindings:

    1. Create a binding and an updater using Roact.createBinding(initialValue) in init().
    2. Pass the binding object directly to a property in render().
    3. Use the updater function to change the value.
    local Foo = Roact.Component:extend("Foo")
    
    function Foo:init()
    	-- createBinding returns (binding, updater)
    	self.clickCount, self.updateClickCount = Roact.createBinding(0)
    end
    
    function Foo:render()
    	return Roact.createElement("TextButton", {
    		-- Roact unwraps the binding and subscribes to it
    		Text = self.clickCount,
    		[Roact.Event.Activated] = function()
    			-- Use the updater to trigger a targeted update
    			self.updateClickCount(self.clickCount:getValue() + 1)
    		end
    	})
    end
  12. Optimize performance with Roact.PureComponent

    master
    Roact.PureComponent is an extension of Roact.Component that implements shouldUpdate using a shallow equality comparison of props and state. It only re-renders if these values change. This is highly effective when using immutable data structures (like those in Rodux) to prevent unnecessary renders.