### Legacy ReactDOM.render Example
Source: https://github.com/roblox/react-luau/blob/main/modules/react-roblox/README.md
This is the most recognizable React entry-point, though it is being phased out in favor of the 'root' APIs.
```javascript
ReactDOM.render(
Hello, world!
,
document.getElementById('root')
);
```
--------------------------------
### Example: Convert Specific File to Luau
Source: https://github.com/roblox/react-luau/blob/main/docs/align-files-guide.md
An example demonstrating the file-by-file conversion command, targeting 'utils.js' within the react-devtools-shared module. Run this from the 'js-to-lua' directory.
```shell
dist/apps/convert-js-to-lua/index.js \
--input ../react/packages/react-devtools-shared/src/utils.js \
--output ../roact-alignment/modules/ \
--rootDir ../react/packages \
--plugin=knownImports --plugin=jestGlobals \
--babelTransformConfig babel-flow-transform-react.config.json \
--babelConfig babel-flow.config.json
```
--------------------------------
### React Memoization Example
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Shows how to use `React.memo` to create a memoized component for performance optimization. Guidance from React JS documentation applies.
```lua
local MyComponent = React.memo(function MyComponent(props)
--[[ render using props ]]
end)
```
--------------------------------
### React Create Element Example
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Illustrates the direct usage of `React.createElement` for creating UI elements in Roact, as JSX is not supported in Luau tooling. This example creates a frame with a child text label.
```lua
--[[
React.createElement(
type,
[props],
[...children]
)]]
local element = React.createElement(
"Frame",
{ Size = UDim2.fromScale(1, 1) },
React.createElement("TextLabel", { Text = "Child1" })
React.createElement("TextLabel", { Text = "Child2" })
)
```
--------------------------------
### RoactCompat Migration Example
Source: https://context7.com/roblox/react-luau/llms.txt
Demonstrates how to use `RoactCompat` to maintain the legacy Roact API surface during migration to React 17+. Only the `require` path needs to change to use the compatibility layer.
```lua
-- Before migration (legacy Roact):
local Roact = require(Packages.Roact)
local tree = Roact.mount(Roact.createElement("TextLabel", { Text = "Hi" }), parent, "UI")
Roact.update(tree, Roact.createElement("TextLabel", { Text = "Updated" }))
Roact.unmount(tree)
-- After (RoactCompat — same API, new renderer):
local Roact = require(Packages.RoactCompat) -- only this line changes
local tree = Roact.mount(Roact.createElement("TextLabel", { Text = "Hi" }), parent, "UI")
Roact.update(tree, Roact.createElement("TextLabel", { Text = "Updated" }))
Roact.unmount(tree)
-- RoactCompat.Ref and RoactCompat.Children map to the string keys used in Roact 17
local ref = Roact.createRef()
Roact.createElement("TextBox", {
[Roact.Ref] = ref, -- equivalent to ref = ref in Roact 17
[Roact.Children] = {}, -- equivalent to children = {} in Roact 17
})
-- Access new React APIs directly while using RoactCompat for old code
local React = require(Packages.React)
local function NewComponent()
local value, setValue = React.useState(0)
return React.createElement("TextLabel", { Text = tostring(value) })
end
-- Mount via legacy API
Roact.mount(Roact.createElement(NewComponent), parent)
```
--------------------------------
### React Component Example
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Demonstrates how to define a React component using `Component:extend` and `init` method for state initialization. It uses `setState` to toggle component state and update the UI.
```lua
local MyComponent = React.Component:extend("MyComponent")
function MyComponent:init()
sself:setState({ expanded = true })
end
function MyComponent:render()
return React.createElement("TextButton", {
Text = if expanded
then self.props.text
else "Click to Expand",
Size = if expanded
then UDim2.new(1, 0, 0, 200)
else UDim2.new(1, 0, 0, 30),
[React.Event.Activated] = function()
self:setState(function(expanded)
return { expanded = not expanded }
end)
end
})
end
```
--------------------------------
### React.useMemo Example
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Caches a computed value between re-renders. Use this to avoid expensive calculations on every render if the dependencies have not changed.
```lua
local function filterByCategory(todos, category)
-- ...
return filteredTodos
end
local function FilteredTodoList(props)
local filteredTodos = React.useMemo(function()
return filterByCategory(props.todos, props.selectedCategory)
end, { props.todos, props.selectedCategory })
return React.createElement(TodoList, {
items = filteredTodos,
})
end
```
--------------------------------
### Render a Simple Message Component in React Luau
Source: https://github.com/roblox/react-luau/blob/main/README.md
This example demonstrates how to create a basic React Luau component that renders a 'Hello' message. It requires importing React and ReactRoblox, and uses `createElement` to define UI elements. The component is then rendered into the player's GUI.
```luau
local React = require(Packages.React)
local ReactRoblox = require(Packages.ReactRoblox)
local e = React.createElement
local function HelloMessage(props: {
name: string,
})
return e("TextLabel", {
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.fromScale(0.5, 0.5),
AutomaticSize = Enum.AutomaticSize.XY,
Text = `Hello, {props.name}!`,
})
end
local function App()
return e("ScreenGui", {}, {
MyMessage = e(HelloMessage, {
name = "Taylor",
}),
})
end
local root = ReactRoblox.createRoot(Instance.new("Folder"))
root:render(ReactRoblox.createPortal(e(App), Players.LocalPlayer.PlayerGui))
```
--------------------------------
### React.useCallback Example
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Caches a function definition to prevent unnecessary re-renders. Use this when passing callbacks to optimized child components that rely on reference equality.
```lua
local ItemTile = require(...)
local equipItem = require(...)
local function EquippableItemTile(props)
local onClick = React.useCallback(function()
equipItem(props.itemId)
end, { props.itemId })
return React.createElement(ItemTile, {
text = props.itemName,
onTileClicked = onClick,
})
end
```
--------------------------------
### TooltipButton Component Example
Source: https://github.com/roblox/react-luau/blob/main/docs/migrating-from-1x/adopt-new-features.md
Defines a `TooltipButton` component that displays a tooltip when clicked. It uses `useState` to manage the tooltip's visibility and `task.delay` to control its fade-out.
```lua
local function TooltipButton(props)
local showTooltip, setShowTooltip = React.useState(false)
return React.createElement("Frame", {
key = "TooltipButton",
Size = UDim2.fromScale(1, 1),
}, {
Button = React.createElement("TextButton", {
Text = "Show tooltip",
Active = props.enabled,
[React.Event.Activated] = function()
setShowTooltip(true)
task.delay(props.tooltipFadeDelay, function()
setShowTooltip(false)
end)
end
})
Tooltip = if showTooltip
then React.createElement("TextLabel", {
Text = "Tooltip text!",
})
else nil
})
end
```
--------------------------------
### Managing State with Bindings using useBinding
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Use useBinding to create a reactive Binding object and its updater function, similar to useState. This example displays the absolute size of an ImageLabel and updates a TextLabel accordingly.
```lua
local function DisplaysSize(props)
local absSize, setAbsSize = React.useBinding(Vector2.new(0, 0))
return React.createElement(React.Fragment, nil,
React.createElement("ImageLabel", {
Image = props.image,
[React.Change.AbsoluteSize] = function(rbx)
setAbsSize(rbx.AbsoluteSize)
end,
}),
React.createElement("TextLabel", {
Text = absSize:map(function(value)
return "X = " .. tostring(value.X) .. "; Y = " .. tostring(value.Y)
end)
}
)
end
```
--------------------------------
### Basic Component with Refs in ReactRoblox
Source: https://github.com/roblox/react-luau/blob/main/modules/react-roblox/README.md
A basic Roact component demonstrating the use of refs for UI elements. This example highlights a potential issue with property assignment order when refs are not backed by binding logic.
```lua
local PopupButtons = Roact.Component:extend("PopupButtons")
function PopupButtons:init()
sself.confirmRef = Roact.createRef()
sself.cancelRef = Roact.createRef()
end
function PopupButtons:render()
--[[
"Some Description"
[ Confirm ] [ Cancel ]
]]
return Roact.createElement("Frame", nil {
ConfirmButton = Roact.createElement("TextButton", {
[Roact.Ref] = self.confirmRef,
Text = "Confirm",
NextSelectionRight = self.cancelRef.value,
}),
CancelButton = Roact.createElement("TextButton", {
[Roact.Ref] = self.cancelRef,
Text = "Confirm",
NextSelectionLeft = self.confirmRef.value,
}),
})
end
```
--------------------------------
### Assigning Instance References with Standard Refs
Source: https://github.com/roblox/react-luau/blob/main/docs/deviations.md
This example demonstrates a common issue when assigning Instance references to properties like NextSelectionRight and NextSelectionLeft. Due to the arbitrary render order of children, refs might be nil when accessed, leading to incorrect assignments.
```lua
local PopupButtons = Roact.Component:extend("PopupButtons")
function PopupButtons:init()
sself.confirmRef = Roact.createRef()
sself.cancelRef = Roact.createRef()
end
function PopupButtons:render()
--[[
"Some Description"
[ Confirm ] [ Cancel ]
]]
return Roact.createElement("Frame", nil {
ConfirmButton = Roact.createElement("TextButton", {
[Roact.Ref] = self.confirmRef,
Text = "Confirm",
NextSelectionRight = self.cancelRef.value,
}),
CancelButton = Roact.createElement("TextButton", {
[Roact.Ref] = self.cancelRef,
Text = "Confirm",
NextSelectionLeft = self.confirmRef.value,
}),
})
end
```
--------------------------------
### Profiling Setup for react-reconciler
Source: https://github.com/roblox/react-luau/blob/main/modules/react-reconciler/README.md
This code demonstrates how to plug in a performance marking function to the global performance object when enabling the Scheduling Profiler. Ensure the `enableProfiling` flag is set in `SchedulerFeatureFlags.js` and `enableSchedulingProfiler` in `ReactFeatureFlags.js`.
```lua
_G.performance = {
mark = function(str)
debug.profileBegin(str)
debug.profileEnd(str)
}
```
--------------------------------
### Creating Bindings with React.createBinding for Class Components
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Explains how to use React.createBinding to create binding objects, similar to useBinding, for use within class components. This example shows creating a binding for Vector2 size and mapping it to a TextLabel.
```lua
local DisplaysSize = React.Component:extend("DisplaysSize")
function DisplaysSize:init()
self.absSize, self.setAbsSize = React.createBinding(Vector2.new(0, 0))
end
function DisplaysSize:render()
return React.createElement(React.Fragment, nil,
React.createElement("ImageLabel", {
Image = self.props.image,
[React.Change.AbsoluteSize] = function(rbx)
self.setAbsSize(rbx.AbsoluteSize)
end,
}),
React.createElement("TextLabel", {
Text = self.absSize:map(function(value)
return "X = " .. tostring(value.X) .. "; Y = " .. tostring(value.Y)
end)
}
)
end
```
--------------------------------
### Using Refs as Bindings for Instance References
Source: https://github.com/roblox/react-luau/blob/main/docs/deviations.md
This revised example shows how to solve the race condition by passing the ref itself (as a binding) instead of its value. This ensures that properties are correctly updated as refs are assigned, regardless of render order.
```lua
-- ...
return Roact.createElement("Frame", nil {
ConfirmButton = Roact.createElement("TextButton", {
[Roact.Ref] = self.confirmRef,
Text = "Confirm",
-- pass the ref itself, which is a binding
NextSelectionRight = self.cancelRef,
}),
CancelButton = Roact.createElement("TextButton", {
[Roact.Ref] = self.cancelRef,
Text = "Confirm",
-- pass the ref itself, which is a binding
NextSelectionLeft = self.confirmRef,
}),
})
-- ...
```
--------------------------------
### React.useReducer Example
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Manages complex state logic using a reducer function. Use this hook when state transitions are complex and involve multiple sub-values or when the next state depends on the previous one.
```lua
local function reducer(state, action)
if action.type == "add" then
return {
value = state.value + action.value,
ops = state.ops + 1,
}
elseif action.type == "multiply" then
return {
value = state.value * action.value,
ops = state.ops + 1,
}
end
end
local function Calculator(props)
local state, dispatch = React.useReducer(reducer, { value = 1, ops = 0 })
return React.createElement(React.Fragment, nil,
React.createElement("TextLabel", {
Text = string.format("value: %d - ops: %d", state.value, state.ops)
})
React.createElement("TextButton", {
Text = "Add 4",
[React.Event.Activated] = function()
dispatch({ type = "add", value = 4 })
end
})
React.createElement("TextButton", {
Text = "Multiply by 3",
[React.Event.Activated] = function()
dispatch({ type = "multiply", value = 3 })
end
})
)
end
```
--------------------------------
### Create React Luau Elements with React.createElement
Source: https://context7.com/roblox/react-luau/llms.txt
Use React.createElement to define UI elements, as Luau lacks JSX. It accepts a type (string for host instances or a component), props, and children. This example shows creating host elements and rendering a function component.
```lua
local React = require(Packages.React)
local ReactRoblox = require(Packages.ReactRoblox)
local e = React.createElement
-- Host element with props and children
local frame = e("Frame", { Size = UDim2.fromScale(1, 1) },
e("TextLabel", { Text = "Hello", LayoutOrder = 1 }),
e("TextLabel", { Text = "World", LayoutOrder = 2 })
)
-- Function component
local function Greeting(props: { name: string })
return e("TextLabel", {
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.fromScale(0.5, 0.5),
AutomaticSize = Enum.AutomaticSize.XY,
Text = `Hello, {props.name}!`,
})
end
local root = ReactRoblox.createRoot(Instance.new("Folder"))
root:render(ReactRoblox.createPortal(
e(Greeting, { name = "Taylor" }),
Players.LocalPlayer.PlayerGui
))
```
--------------------------------
### Legacy Roact Mounting, Updating, and Unmounting
Source: https://github.com/roblox/react-luau/blob/main/docs/migrating-from-1x/convert-legacy-conventions.md
Demonstrates the traditional Roact lifecycle management using Roact.mount, Roact.update, and Roact.unmount.
```lua
local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui
local Roact = require(Packages.Roact)
local roactTree = Roact.mount(Roact.createElement("TextLabel", {
Text = "Hello world!",
}, PlayerGui)
task.wait(3)
roactTree = Roact.update(roactTree, Roact.createElement("TextLabel", {
Text = "Hello Roblox!",
})
task.wait(3)
Roact.unmount(roactTree)
```
--------------------------------
### React.useRef Example
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Stores a mutable value that does not trigger re-renders when changed. Useful for accessing Roblox Instances or holding any mutable data.
```lua
local function TextBoxWithButton(props)
local textBoxRef = React.useRef(nil)
return React.createElement(React.Fragment, nil,
React.createElement("TextBox", { ref = textBoxRef }),
React.createElement("TextButton", {
Text = "->",
[React.Event.Activated] = function()
textBoxRef.current:CaptureFocus()
end
}),
)
end
```
--------------------------------
### Count Children with React.Children.count
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Get the total number of child members in props.children using React.Children.count. Note that userdata children are not included in the count.
```lua
local function List(props)
print(string.format("Render list with %d children", React.Children.count(props.children)))
return React.createElement(
"Frame",
{ Size = UDim2.fromScale(1, 1) },
props.children,
)
end
```
--------------------------------
### RoactCompat.Ref Usage
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/roact-compat.md
Demonstrates how RoactCompat.Ref allows legacy Roact prop tables to be equivalent to Roact 17 prop tables using the 'ref' prop.
```lua
Roact.createElement("TextLabel", {
Text = "Hello",
[Roact.Ref] = textLabelRef,
})
```
```lua
Roact.createElement("TextLabel", {
Text = "Hello",
ref = textLabelRef,
})
```
--------------------------------
### connectToDevtools
Source: https://github.com/roblox/react-luau/blob/main/modules/react-devtools-core/README.md
Sets up a connection to DevTools. This function should be run in the same context as React and before importing any React packages.
```APIDOC
## connectToDevtools(config)
### Description
Connects to the React DevTools.
### Method
`connectToDevtools`
### Parameters
#### Config Object
- **host** (string) - Optional - The host to connect to. Defaults to "localhost".
- **port** (number) - Optional - The port to connect to. Defaults to `8097`.
- **useHttps** (boolean) - Optional - Whether to use a secure WebSocket protocol (wss). Defaults to `false`.
- **resolveRNStyle** (`(style: number) => ?Object`) - Optional - A function used by the React Native style plug-in.
- **isAppActive** (`() => boolean`) - Optional - A function that DevTools will poll. Connection will wait until this function returns `true`.
```
--------------------------------
### Marking ROBLOX Deviations
Source: https://github.com/roblox/react-luau/blob/main/docs/align-files-guide.md
Example of how to mark code deviations in Luau. Uncomment the original line, wrap it with deviation comments, and provide the corrected implementation.
```lua
-- ROBLOX deviation START:
-- Boolean.toJSBoolean(value)
-- ROBLOX deviation END
value
```
--------------------------------
### Roact 17 Mounting, Updating, and Unmounting with createRoot
Source: https://github.com/roblox/react-luau/blob/main/docs/migrating-from-1x/convert-legacy-conventions.md
Shows the Roact 17 approach using ReactRoblox.createRoot for managing UI lifecycle, including rendering and unmounting via root:render(nil).
```lua
local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui
local React = require(Packages.React)
local ReactRoblox = require(Packages.ReactRoblox)
-- Roact 17 roots will take full ownership of the instance provided to them,
-- so we should not create a root using PlayerGui directly
local container = Instance.new("Folder")
container.Parent = PlayerGui
local root = ReactRoblox.createRoot(container)
root:render(Roact.createElement("TextLabel", {
Text = "Hello world!",
})
task.wait(3)
root:render(Roact.createElement("TextLabel", {
Text = "Hello Roblox!",
})
task.wait(3)
root:render(nil)
```
--------------------------------
### Mapping Binding Value for Display
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Use the `map` method on a binding to transform its value for display or use in other properties. This example formats a click count into a user-friendly string.
```lua
local function UpdateOnClick()
local count, setCount = React.useBinding(0)
return React.createElement("TextButton", {
Text = count:map(function(value)
return string.format("Clicked %d times", value)
end),
[React.Event.Activated] = function()
setCount(count:getValue() + 1)
end,
})
end
```
--------------------------------
### RoactCompat.mount
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/roact-compat.md
Compatibility method mimicking legacy Roact.mount. It creates a root, renders the element, and returns a handle for updates and unmounting.
```APIDOC
## RoactCompat.mount
### Description
Compatibility method mimicking [legacy `Roact.mount`](https://roblox.github.io/roact/api-reference/#roactmount).
For all intents and purposes, this should function equivalently to legacy Roact's `mount` function. Under the hood, RoactCompat takes the following steps:
1. Creates a root using [`React.createRoot`](react.md#reactcreateroot)
* When `_G.__ROACT_17_COMPAT_LEGACY_ROOT__` is enabled, this will use [`React.createLegacyRoot`](react.md#reactcreatelegacyroot) instead
2. Calls `root:render` with the provided element
* React's roots take complete control of the provided container, deleting all existing children. Legacy Roact does not tamper with existing children of the provided container. To mimic the legacy behavior, we use a [`Portal`](react.md#reactcreateportal) to mount into the container instead of providing it directly to the root.
* When `_G.__ROACT_17_INLINE_ACT__` is enabled, the `render` call is automatically wrapped in [`ReactRoblox.act`](react-roblox.md#reactrobloxact) to ensure that mounting behavior resolves synchronously in tests.
3. Returns an opaque handle to the root that can be used with [`RoactCompat.update`](#roactcompatupdate) and [`RoactCompat.unmount`](#roactcompatunmount)
### Method Signature
```lua
RoactCompat.mount(element: ReactElement, container: Instance?, name: string?): RoactTree
```
```
--------------------------------
### Customizing Component Interface with useImperativeHandle
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Use useImperativeHandle to expose a custom interface from a component to its parent via a ref. This example shows how to expose a `scrollToTop` function to control a ScrollingFrame.
```lua
local ControllableScrollingFrame = React.forwardRef(function(props, ref)
local innerRef = React.useRef(nil)
React.useImperativeHandle(ref, function()
return {
scrollToTop = function()
innerRef.current.CanvasPosition = Vector2.new(0, 0)
end
}
end, {})
return React.createElement("ScrollingFrame", {
ref = innerRef
-- ...
})
end)
-- Another component will be able to access this interface via a ref:
local function TodoListApp(props)
local scrollingFrameRef = React.useRef(nil)
return React.createElement(ControllableScrollingFrame,
{
ref = scrollingFrameRef
},
React.createElement(TodoList, {
items = props.todoItems,
}),
React.createElement("TextButton", {
Text = "Back to top",
[React.Event.Activated] = function()
scrollingFrameRef.current.scrollToTop()
end
})
)
end
```
--------------------------------
### Legacy Implicit Ref Forwarding vs Explicit Ref Forwarding
Source: https://github.com/roblox/react-luau/blob/main/docs/migrating-from-1x/minimum-requirements.md
Demonstrates the legacy method of implicit ref forwarding using `Roact.Ref` and the Roact 17+ compatible explicit ref forwarding using `Roact.forwardRef`.
```lua
local function FancyButton(props)
return Roact.createElement("TextBox", {
PlaceholderText = "Enter your text here",
PlaceholderColor3 = Color3.new(0.4, 0.4, 0.4),
[Roact.Change.Text] = props.onTextChange,
-- Implicitly forwarding a ref via the `Roact.Ref` prop
[Roact.Ref] = props[Roact.Ref],
})
end
```
```lua
local FancyButton = Roact.forwardRef(function(props, ref)
return Roact.createElement("TextBox", {
PlaceholderText = "Enter your text here",
PlaceholderColor3 = Color3.new(0.4, 0.4, 0.4),
[Roact.Change.Text] = props.onTextChange,
-- Explicitly forwarding a ref passed in via `forwardRef`
[Roact.Ref] = ref,
end)
end)
```
--------------------------------
### Roact Warning for Duplicate Key Methods
Source: https://github.com/roblox/react-luau/blob/main/docs/deviations.md
Roact issues a warning if both table keys and the 'key' prop are used simultaneously for the same component. This example demonstrates a scenario that triggers such a warning.
```lua
return React.createElement("Frame", nil, {
Label = React.createElement("TextLabel", {
key = "label1",
Text = "Hello",
})
})
```
--------------------------------
### Require and Connect to DevTools
Source: https://github.com/roblox/react-luau/blob/main/modules/react-devtools-core/README.md
Require the package and call `connectToDevtools` before importing any React packages. This sets up the connection to DevTools with optional configuration.
```luau
local ReactDevtoolsCore = require(Packages.ReactDevtoolsCore)
local connectToDevtools = ReactDevtoolsCore.connectToDevtools
connectToDevTools(config)
```
--------------------------------
### Testing Tooltip Behavior with `act`
Source: https://github.com/roblox/react-luau/blob/main/docs/migrating-from-1x/adopt-new-features.md
Demonstrates how to use `ReactRoblox.act` to test component behavior, including rendering, state changes, and event handling. It ensures all scheduled work is flushed before assertions.
```lua
local React = require(Packages.React)
local ReactRoblox = require(Packages.ReactRoblox)
local container, root
beforeEach(function()
container = Instance.new("ScreenGui")
container.Parent = Players.LocalPlayer.PlayerGui
root = ReactRoblox.createRoot(container)
end)
afterEach(function()
container:Destroy()
end)
it("shows a tooltip on click and hides it after a delay", function()
-- Use `act` for the initial render
ReactRoblox.act(function()
-- Render the button in a disabled state
root:render(React.createElement(TooltipButton, {
enabled = false,
tooltipFadeDelay = 1,
}))
end)
expect(container.TooltipButton.Tooltip).toBeNil()
expect(container.TooltipButton.Button.Active).toBe(false)
-- Use `act` to re-render the tree
ReactRoblox.act(function()
-- Rerender in the enabled state
root:render(React.createElement(TooltipButton, {
enabled = true,
tooltipFadeDelay = 1,
}))
end)
expect(container.TooltipButton.Tooltip).toBeNil()
expect(container.TooltipButton.Button.Active).toBe(true)
-- Use `act` to trigger virtual input
local element = Rhodium.Element.new(container.TooltipButton.Button)
ReactRoblox.act(function()
-- Click the button to trigger the tooltip
element:click()
Rhodium.VirtualInput.waitForInputEventsProcessed()
end)
expect(container.TooltipButton.Tooltip).never.toBeNil()
expect(container.TooltipButton.Tooltip.Text).toEqual("Tooltip text!")
-- Use `act` to resume queued renders after a delayed callback fires
ReactRoblox.act(function()
task.wait(1)
end)
expect(container.TooltipButton.Tooltip).toBeNil()
end)
```
--------------------------------
### Update Legacy Component Lifecycle Names
Source: https://github.com/roblox/react-luau/blob/main/docs/migrating-from-1x/convert-legacy-conventions.md
Update legacy lifecycle method names to match React JS conventions for better ecosystem compatibility. For example, `didMount` should be renamed to `componentDidMount`.
```lua
local FocusButton = Roact.Component:extend("FocusButton")
function FocusButton:init()
sself.ref = Roact.createRef()
end
function FocusButton:render()
return Roact.createElement("Button", {
Size = self.props.Size,
[Roact.Ref] = self.ref
}, self.props[Roact.Children])
end
function FocusButton:didMount()
GuiService.SelectedObject = self.ref.current
end
```
```lua
local FocusButton = Roact.Component:extend("FocusButton")
function FocusButton:init()
sself.ref = Roact.createRef()
end
function FocusButton:render()
return Roact.createElement("Button", {
Size = self.props.Size,
ref = self.ref
}, self.props.children)
end
function FocusButton:didMount()
GuiService.SelectedObject = self.ref.current
end
```
--------------------------------
### Updating Binding Value Relative to Current Value
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Use `getValue` to retrieve the current value of a binding, useful for updates relative to the current state. This example increments a counter when a button is activated.
```lua
local function UpdateOnClick()
local count, setCount = React.useBinding(0)
return React.createElement("TextButton", {
Text = count,
[React.Event.Activated] = function()
setCount(count:getValue() + 1)
end,
})
end
```
--------------------------------
### Handling UI Events and Properties with React.Event, React.Change, and React.Tag
Source: https://context7.com/roblox/react-luau/llms.txt
Utilize special prop keys like `React.Event` for Instance events, `React.Change` for property change signals, and `React.Tag` for `CollectionService` tags. These are automatically managed on mount and unmount.
```lua
local React = require(Packages.React)
local function InteractiveFrame()
local hovered, setHovered = React.useState(false)
local scrollPos, setScrollPos = React.useState(Vector2.new(0, 0))
return React.createElement("Frame", {
Size = UDim2.fromScale(1, 1),
BackgroundColor3 = if hovered
then Color3.fromRGB(200, 200, 255)
else Color3.fromRGB(255, 255, 255),
-- React.Event: connect to MouseEnter / MouseLeave events
[React.Event.MouseEnter] = function()
setHovered(true)
end,
[React.Event.MouseLeave] = function()
setHovered(false)
end,
-- React.Tag: apply CollectionService tags on mount
[React.Tag] = "interactive-frame ui-element",
},
React.createElement("ScrollingFrame", {
Size = UDim2.fromScale(1, 0.8),
CanvasSize = UDim2.fromScale(1, 3),
-- React.Change: subscribe to a property changed signal
[React.Change.CanvasPosition] = function(rbx)
setScrollPos(rbx.CanvasPosition)
end,
}),
React.createElement("TextLabel", {
Text = string.format("Scroll: %.0f, %.0f", scrollPos.X, scrollPos.Y),
Size = UDim2.new(1, 0, 0, 30),
})
)
end
```
--------------------------------
### Legacy Context API vs Roact 17+ Context API
Source: https://github.com/roblox/react-luau/blob/main/docs/migrating-from-1x/minimum-requirements.md
Compares the legacy method of reading style from context with the Roact 17+ compatible method using `createContext` and `StyleContext.Provider`/`StyleContext.Consumer`.
```lua
local AppStyle = require(script.Parent.AppStyle)
local Label = Roact.Component:extend("Label")
function Label:init()
-- reading style from context
self.style = self._context.style
end
function Label:render()
return Roact.createElement("TextLabel", {
BackgroundColor3 = self.style.LabelColor,
Text = props.text,
})
end
local App = Roact.Component:extend("App")
function App:init()
-- defining style in context
self._context.style = AppStyle
end
function App:render()
return Roact.createElement("Frame", {
Size = UDim2.fromScale(1, 1)
}, {
Start = Roact.createElement(Button, {
text = "Hello World",
})
})
end
```
```lua
local AppStyle = require(script.Parent.AppStyle)
local StyleContext = Roact.createContext(nil)
local Label = Roact.Component:extend("Label")
function Label:render()
return Roact.createElement(StyleContext.Consumer, {
render = function(style)
return Roact.createElement("TextLabel", {
BackgroundColor3 = style.LabelColor,
Text = props.text,
end
})
end
end)
end
local App = Roact.Component:extend("App")
function App:render()
return Roact.createElement(StyleContext.Provider, {
value = AppStyle,
}, {
App = Roact.createElement("Frame", {
Size = UDim2.fromScale(1, 1)
}, {
Start = Roact.createElement(Button, {
text = "Hello World",
})
})
})
end
```
--------------------------------
### Getting the Single Child from Children with RoactCompat
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/roact-compat.md
RoactCompat.oneChild retrieves a single child from a collection of children, providing functionality similar to React.Children.only but with added capability to unwrap a table containing a single element.
```luau
RoactCompat.oneChild(
children: { [string | number]: ReactElement } | ReactElement | nil
): ReactElement
```
--------------------------------
### Optimizing with useCallback and useMemo
Source: https://context7.com/roblox/react-luau/llms.txt
Use `useMemo` to cache the result of expensive computations, recomputing only when dependencies change. Use `useCallback` to memoize function references, ensuring stable references for child components unless dependencies change.
```lua
local React = require(Packages.React)
local function expensiveFilter(items, category)
-- simulate expensive computation
local result = {}
for _, item in items do
if item.category == category then
table.insert(result, item)
end
end
return result
end
local function ItemList(props)
-- useMemo: recompute filtered list only when inputs change
local filteredItems = React.useMemo(function()
return expensiveFilter(props.items, props.selectedCategory)
end, { props.items, props.selectedCategory })
-- useCallback: stable handler reference for child components
local handleSelect = React.useCallback(function(itemId)
props.onSelectItem(itemId)
end, { props.onSelectItem })
local children = {}
for i, item in filteredItems do
children[item.id] = React.createElement("TextButton", {
Text = item.name,
LayoutOrder = i,
[React.Event.Activated] = function() handleSelect(item.id) end,
})
end
return React.createElement("Frame", { Size = UDim2.fromScale(1, 1) }, children)
end
```
--------------------------------
### Applying CollectionService Tags with React.Tag
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Demonstrates how to apply CollectionService tags to a host component using React.Tag. Multiple tags can be applied by providing a space-delimited string. Tags are applied when the component mounts.
```lua
local button = Roact.createElement("TextButton", {
[React.Tag] = "confirm-button"
Text = "Confirm",
-- ...
})
```
```lua
[React.Tag] = "some-tag some-other-tag"
```
--------------------------------
### RoactCompat.Portal
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/roact-compat.md
Compatibility component mimicking Roact.Portal. It uses React.createPortal under the hood.
```APIDOC
## RoactCompat.Portal
### Description
Compatibility component mimicking [`Roact.Portal`](https://roblox.github.io/roact/api-reference/#roactportal). Uses the [React.createPortal](react.md#reactcreateportal) function under the hood.
### Usage
```lua
local RoactCompat = require(path.to.RoactCompat)
local MyPortal = RoactCompat.Portal
-- Usage within a component:
local element = { ...children... }
```
```
--------------------------------
### RoactCompat.Children Usage
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/roact-compat.md
Shows how RoactCompat.Children enables legacy Roact prop tables to be equivalent to Roact 17 prop tables using the 'children' prop for forwarding children.
```lua
-- forwards the children provided to this component
Roact.createElement("Frame", nil, self.props[Roact.Children])
```
```lua
-- forwards the children provided to this component
Roact.createElement("Frame", nil, self.props.children)
```
--------------------------------
### Combine Multiple Bindings with React.joinBindings
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Use React.joinBindings to merge several bindings into one. The resulting binding updates when any of the input bindings change. This example demonstrates combining two size bindings to dynamically set the size of a parent Frame.
```lua
local function Flex() local aSize, setASize = React.useBinding(Vector2.new()) local bSize, setBSize = React.useBinding(Vector2.new()) return React.createElement( "Frame", { Size = React.joinBindings({aSize, bSize}):map(function(sizes) local sum = Vector2.new() for _, size in sizes do sum += size end return UDim2.new(0, sum.X, 0, sum.Y) end), }, React.createElement("Frame", { Size = UDim2.new(1, 0, 0, 30), [React.Change.AbsoluteSize] = function(instance) setASize(instance.Size) end, }), React.createElement("Frame", { Size = UDim2.new(1, 0, 0, 30), Position = aSize:map(function(size) return UDim2.new(0, 0, 0, size.Y) end), [React.Change.AbsoluteSize] = function(instance) setBSize(instance.Size) end, }) ) end
```
--------------------------------
### Migrate Reserved 'key' Prop to 'order' in Roact Components
Source: https://github.com/roblox/react-luau/blob/main/docs/migrating-from-1x/minimum-requirements.md
When migrating to Roact 17+, avoid using 'key' as a prop name. This example shows how to replace 'key' with 'order' to prevent conflicts with Roact's internal key management.
```lua
local function OptionButton(props)
return Roact.createElement("TextButton", {
LayoutOrder = props.key,
Text = props.text,
[Roact.Event.Activated] = props.onClick,
})
end
local function ButtonGroup(props)
return Roact.createFragment({
CancelButton = Roact.createElement(OptionButton, {
key = 1,
text = "Cancel",
onClick = props.cancelCallback,
})
ConfirmButton = Roact.createElement(OptionButton, {
key = 2,
text = "Confirm",
onClick = props.confirmCallback,
})
})
end
```
```lua
local function OptionButton(props)
return Roact.createElement("TextButton", {
LayoutOrder = props.order,
Text = props.text,
[Roact.Event.Activated] = props.onClick,
})
end
local function ButtonGroup(props)
return Roact.createFragment({
CancelButton = Roact.createElement(OptionButton, {
order = 1,
text = "Cancel",
onClick = props.cancelCallback,
})
ConfirmButton = Roact.createElement(OptionButton, {
order = 2,
text = "Confirm",
onClick = props.confirmCallback,
})
})
end
```
--------------------------------
### Convert Legacy Roact Portal to Roact 17
Source: https://github.com/roblox/react-luau/blob/main/docs/migrating-from-1x/convert-legacy-conventions.md
Roact 17 uses `ReactRoblox.createPortal` for creating portals, differing from the legacy Roact syntax which used `Roact.Portal` with a target prop.
```lua
local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui
local function Modal(props)
return Roact.createElement(Roact.Portal, {
target = PlayerGui,
}, {
Modal = Roact.createElement("ScreenGui", {}, {
Label = Roact.createElement("TextButton", {
Text = "Click me to close!",
[Roact.Event.Activated] = props.onClose,
})
})
})
end
```
```lua
local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui
local function Modal(props)
return ReactRoblox.createPortal({
Modal = Roact.createElement("ScreenGui", {}, {
Label = Roact.createElement("TextButton", {
Text = "Click me to close!",
[Roact.Event.Activated] = props.onClose,
})
})
}, PlayerGui)
end
```
--------------------------------
### Conditionally Swap Roact API at Runtime
Source: https://github.com/roblox/react-luau/blob/main/docs/migrating-from-1x/upgrading-to-roact-17.md
In your entry point script, check a global flag to determine whether to overwrite the Roact API with RoactCompat. This allows testing with Roact 17 before full adoption.
```lua
if _G.__NEW_ROACT__ then
local Roact = require(Packages.Roact)
local RoactCompat = require(Packages.Dev.RoactCompat)
-- Overwrite the contents of the `Roact` package with that of the
-- RoactCompat package
for api, _ in Roact do
Roact[api] = RoactCompat[api]
end
end
-- Proceed with test setup...
```
--------------------------------
### Mounting a Roact Element with RoactCompat
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/roact-compat.md
Use RoactCompat.mount to render a ReactElement into a container, mimicking legacy Roact behavior. It creates a root, renders the element, and returns a handle for updates and unmounting. Note that React's roots take full control of the container, so a Portal is used to preserve existing children.
```luau
RoactCompat.mount(
element: ReactElement,
container: Instance?,
name: string?
): RoactTree
```
--------------------------------
### Roact 'init' Lifecycle Method
Source: https://github.com/roblox/react-luau/blob/main/docs/deviations.md
The 'init' method serves as the constructor for Roact class components, running immediately after instance creation. It replaces the need for `super` calls found in other languages.
```lua
local MyComponent = React.Component:extend("MyComponent")
function MyComponent:init(props)
sself.state = { value = 0 }
end
function MyComponent:render()
return React.createElement("TextLabel", {Text = self.props.text})
end
```
--------------------------------
### Animating Transparency with React.useBinding
Source: https://context7.com/roblox/react-luau/llms.txt
Use `useBinding` to directly update Instance properties for high-frequency changes like animations without triggering re-renders. Ensure `RunService.Heartbeat` is connected for continuous updates.
```lua
local React = require(Packages.React)
-- useBinding: animate a transparency value without triggering re-renders
local function AnimatedLabel(props)
local transparency, setTransparency = React.useBinding(0)
React.useEffect(function()
-- Drive transparency directly via binding update
local connection = RunService.Heartbeat:Connect(function(dt)
setTransparency((os.clock() % 2) / 2) -- pulses 0–1
end)
return function() connection:Disconnect() end
end, {})
return React.createElement("TextLabel", {
Text = props.text,
-- Binding applied directly; no re-render needed
BackgroundTransparency = transparency,
Size = UDim2.fromScale(1, 0.1),
})
end
```
--------------------------------
### ReactRoblox.createLegacyRoot
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react-roblox.md
Creates a legacy root, similar to pre-concurrent React behavior. Refer to ReactDOM's documentation for details.
```APIDOC
## ReactRoblox.createLegacyRoot
Adopted as part of the Concurrent Mode API. "Legacy" roots are essentially equivalent to pre-concurrent React behavior. Refer to [`ReactDOM.createLegacyRoot` documentation](https://reactjs.org/docs/concurrent-mode-adoption.html#migration-step-blocking-mode).
```
--------------------------------
### RoactCompat.unmount
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/roact-compat.md
Compatibility method mimicking legacy Roact.unmount. It unmounts a Roact tree from its container.
```APIDOC
## RoactCompat.unmount
### Description
Compatibility method mimicking [legacy `Roact.unmount`](https://roblox.github.io/roact/api-reference/#roactunmount).
### Method Signature
```lua
RoactCompat.unmount(tree: RoactTreeHandle)
```
```
--------------------------------
### ReactRoblox.createRoot
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react-roblox.md
Creates a root for concurrent mode rendering. Refer to ReactDOM's documentation for detailed usage.
```APIDOC
## ReactRoblox.createRoot
Adopted as part of the Concurrent Mode API. Refer to [`ReactDOM.createRoot` documentation](https://reactjs.org/docs/concurrent-mode-reference.html#createroot).
```
--------------------------------
### RoactCompat.createFragment
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/roact-compat.md
Compatibility method mimicking Roact.createFragment. It creates a fragment element.
```APIDOC
## RoactCompat.createFragment
### Description
Compatibility method mimicking [`Roact.createFragment`](https://roblox.github.io/roact/api-reference/#roactcreatefragment). Uses the special component [`React.Fragment`](react.md#reactfragment) under the hood.
### Method Signature
```lua
RoactCompat.createFragment(elements: { [string | number]: ReactElement }): ReactElement
```
```
--------------------------------
### React.Component
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/react.md
Documentation for creating class components in React for Roblox. It details differences from React JS, such as using `Component:extend` and an `init` method instead of a constructor, and state initialization via `setState`.
```APIDOC
## React.Component
### Description
Luau does not have ES6's class semantics, so class components work slightly differently from React JS. Use `Component:extend` in place of ES6 class semantics and implement an `init` method on components instead of a constructor. State is initialized using `setState`.
### Example
```lua
local MyComponent = React.Component:extend("MyComponent")
function MyComponent:init()
sself:setState({ expanded = true })
end
function MyComponent:render()
return React.createElement("TextButton", {
Text = if expanded
then self.props.text
else "Click to Expand",
Size = if expanded
then UDim2.new(1, 0, 0, 200)
else UDim2.new(1, 0, 0, 30),
[React.Event.Activated] = function()
self:setState(function(expanded)
return { expanded = not expanded }
end)
end
})
end
```
```
--------------------------------
### Updating a Roact Tree with RoactCompat
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/roact-compat.md
Use RoactCompat.update to re-render an existing Roact tree with a new element. The 'tree' argument must be a handle obtained from a previous RoactCompat.mount or RoactCompat.update call.
```luau
RoactCompat.update(tree: RoactTree, element: ReactElement): RoactTree
```
--------------------------------
### Using setState in Roact 'init' Method
Source: https://github.com/roblox/react-luau/blob/main/docs/deviations.md
It is recommended to use `setState` within the 'init' method (constructor equivalent) in Roact 17+. This avoids direct assignment to `self.state` and ensures consistent state updates.
```lua
local MyComponent = React.Component:extend("MyComponent")
function MyComponent:init(props)
sself.setState({ count = 0 })
end
function MyComponent:render()
return React.createElement("TextLabel", {Text = "Count: " .. self.state.count})
end
```
--------------------------------
### RoactCompat.update
Source: https://github.com/roblox/react-luau/blob/main/docs/api-reference/roact-compat.md
Compatibility method mimicking legacy Roact.update. It updates an existing Roact tree with a new element.
```APIDOC
## RoactCompat.update
### Description
Compatibility method mimicking [legacy `Roact.update`](https://roblox.github.io/roact/api-reference/#roactupdate).
The first argument should be the value returned from a prior call to [`RoactCompat.mount`](#roactcompatmount) or `RoactCompat.update`. This function will not work if the argument passed in was created with legacy Roact.
### Method Signature
```lua
RoactCompat.update(tree: RoactTree, element: ReactElement): RoactTree
```
```