Minimum Requirements¶
When upgrading to React Lua, a small set of legacy patterns and features need to be fixed in order to maintain intended behavior. Once these conditions are met, your legacy Roact code should work as expected in React Lua.
All of these requirements can be met using APIs available in legacy Roact, though most will require a minimum version. This means that a Roact codebase can safely be made compatible with React Lua as a preliminary step before adopting it.
No Reserved Props¶
In React Lua, components cannot rely on any reserved prop keywords:
- "ref" - reserved by React to assign refs, equivalent to legacy Roact's
Roact.Ref
- "key" - reserved by React to assign stable keys to children
- "children" - reserved by React as a special prop representing the children passed down to the component
If your component is using "ref" or "key" as the name of one of its props, those props will no longer be populated with a value in React Lua.
Additionally, if it's using "children" as the name of one of its props, the value of the "children" prop will become the table of child elements instead of the value provided by the parent component in React Lua.
Info
This restriction does not involve legacy APIs, so this migration can be completed in codebases depending upon any version of legacy Roact.
Example¶
Suppose we have a component OptionButton
and a separate component ButtonGroup
that uses it.
The OptionButton
component is using a prop called key
to pass through to its LayoutOrder. In React Lua, this prop will be nil because it will be consumed by Roact and used as a stable key.
To fix this, we replace the use of the key
prop with a prop with a different name.
Legacy¶
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
React Lua Compatible¶
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
No Legacy Context¶
Legacy Roact implemented a _context
field on all component instances as an alternative implementation for the Context feature. This is deprecated in legacy Roact and is not supported in React Lua. Attempting to access fields on self._context
in React Lua will throw an error.
How To Convert¶
Replace any uses of _context
with the Provider and Consumer pattern via createContext
. This is the preferred pattern in legacy Roact as well because it allows Roact to trigger updates on context consumers when context providers pass in a new value.
Generally, you'll take the following steps:
- Create a context object that you will use in place of
_context
by callingcreateContext
and saving the result, usually as the return value of a separateModuleScript
. - Wherever you have a component that writes to
self._context
, instead wrap the component's children in aContext.Provider
component and provide the value that was previously being written toself._context
. - Wherever you have a component that reads from
self._context
, instead wrap that component's children in aContext.Consumer
component. The Consumer accepts therender
prop, which is a function that accepts a context value and returns a React element.
Info
The createContext
API is available in legacy Roact 1.3.0 (or newer) and is fully supported in React Lua.
Example¶
Suppose we have a style
object that must be provided to all children of our app. We define it in our top-level App
component and read from it in our Label
component.
Legacy¶
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
React Lua Compatible¶
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
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
Explicit Ref Forwarding¶
Legacy Roact uses Roact.Ref
as a special prop key to support the refs feature. Assigning the [Roact.Ref]
property to a callback ref or ref object allows Roact to assign its value. However, Roact only interacts with the Roact.Ref
property if the component receiving the props is a host component.
Some class component definitions rely on this behavior by accepting and reassigning the [Roact.Ref]
prop themselves, knowing that Roact won't capture it. The pattern of passing a provided ref onto a child is called "ref forwarding". We refer to using [Roact.Ref]
as mechanism of ref forwarding as "implicit ref forwarding".
How To Convert¶
In React Lua, Roact.Ref
is aliased to the string "ref", and refs that point to class components are now supported. Components that were using implicit ref forwarding will fail to forward their provided refs when upgrading to React Lua.
Fortunately, this can be easily fixed with the forwardRef
function. We refer to this as "explicit ref forwarding".
Info
The forwardRef
API is available in legacy Roact 1.4.0 (or newer) and is fully supported in React Lua.
Example¶
Suppose we have a FancyTextBox
component that accepts a ref, and passes it on to an underlying TextBox
. Rather than accepting the [Roact.Ref]
prop, we should use the Roact.forwardRef
wrapper to explicitly accept a ref and assign it to the TextBox
.
Legacy¶
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
React Lua Compatible¶
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)
You can see a full example of forwardRef
migration in this UIBlox PR.
Prefer getDerivedStateFromProps¶
Legacy Roact allows class components to implement both willUpdate
and getDerivedStateFromProps
lifecycle methods.
React JS, however, does not support both methods when implemented on the same component. When getDerivedStateFromProps
is defined, it replaces componentWillUpdate
entirely. React Lua inherits this restriction: getDerivedStateFromProps
will replace willUpdate
if both are defined.
How To Convert¶
In order to make existing components React Lua compatible, make sure to use either willUpdate
or getDerivedStateFromProps
, but not both.
Whenever possible, use getDerivedStateFromProps
to resolve interactions between state and props. Just like in React JS 16.3.0 and onward, willUpdate
is a deprecated legacy lifecycle method and should be avoided as it can exacerbate problems with asynchronous rendering, a flagship feature of React Lua.
Info
The getDerivedStateFromProps
static lifecycle method is supported in legacy Roact as far back as Roact 0.2.0 and is fully supported in React Lua.
Example¶
Typically, usage of both willUpdate
and getDerivedStateFromProps
is a sign of an overly complicated component, which makes it difficult to provide a simplified example that's meaningful.
Refer to the React JS guidance on migrating away from legacy lifecycle methods.