VirtualInstance
VirtualInstances are the basic building blocks for Dex applications.
VirtualInstances are "Mounted" by Dex's reconciler engine, creating/destoying/modifying these real instances automatically as needed.
Constructors
Dex.New
Dex.New(
className: string,
props: {[string]: any}?,
children: {[any]: CanBeObservable<VirtualInstance?>}
) -> VirtualInstance
Creates a new VirtualInstance that represents a newly-created Roblox
Instance (via Instance.new(className)
).
Dex.Clone
Dex.Clone(
template: Instance | VirtualInstance,
props: {[string]: any}?,
children: {[any]: CanBeObservable<VirtualInstance?>}
) -> VirtualInstance
Creates a new VirtualInstance that represents a cloned Roblox Instance
from a given template instance (via template:Clone()
).
Dex.Premade
Dex.Premade(
className: stirng,
props: {[string]: any}?,
children: {[any]: CanBeObservable<VirtualInstance?>}
) -> VirtualInstance
Creates a new VirtualInstance that represents a pre-existing Roblox Instance to be modified by Dex.
If passed into the the Render function for a Dex.Root component, the root instance will be used used.
Functions
SetProperties
VirtualInstance:
SetProperties
(
propertyMap:
{
[
string
]
:
CanBeObservable
<
any
>
}
) →
(
)
Adds properties to be rendered on the instance when it is reconciled.
Multiple calls to this function will merge the provided property map with the existing one, prioritizing the later call.
Values in the propertyMap can have multiple possible types, which are each handled differently:
- A Static Value (number, boolean, string, UDim2, etc.) to be assigned right as the VirtualInstance is rendered
- An Observablue Value to be assigned as the VirtualInstance is Rendered, as well as when the Observable value changes
- A
Dex.Nil
symbol, which assigns a property tonil
when rendered - A function which represents a listener to connect to an event while the VirtualInstance is rendered
- Another VirtualInstance, which references an instance property to be assigned. This is useful for ViewportFrames, for example, where a camera must both be created underneath the ViewportFrame, and assigned as the CurrentCamera property within the same frame.
SetAttributes
VirtualInstance:
SetAttributes
(
attributeMap:
{
[
string
]
:
CanBeObservable
<
any
>
}
) →
(
)
Adds attributes to be rendered on the instance when it is reconciled.
Multiple calls to this function will merge the provided attribute map with the existing one, prioritizing the latest calls.
AddTags
VirtualInstance:
AddTags
(
tags:
CanBeObservable
<
{
string
}
>
) →
(
)
Adds tags to the Virtual Instance when it is reconciled. Multiple calls to this function will add extra tags to the VirtualInstance if they do not exist.
Connect
VirtualInstance:
Connect
(
eventName:
string
,
listener:
(
...any
)
→
(
)
) →
(
)
Adds an event listener to the Virtual Instance, which will automatically be connected and disconnected as the VirtualInstance is reconciled.
OutProperty
Creates an Observable object that updates to the current value of an property on the virtual instance once when it is mounted, and listens to updates on this property.
Give initialValue a type annotation if initializing to nil (e.g. vInst:OutProperty("Adornee", nil :: Instance?)()
OutInitialProperty
VirtualInstance:
OutInitialProperty
(
propName:
string
,
initialValue:
T
) →
Observable
<
T
>
-
An
observable
with
the
same
output
type
as
initialValue.
Creates an Observable object that updates to the original value of an property on the virtual instance once when it is mounted. This is useful for referencing properties on a premade instance without hardcoding them in a Dex component.
Give initialValue a type annotation if initializing to nil (e.g. vInst:OutInitialProperty("Adornee", nil :: Instance?))
OutAttribute
Creates an Observable object that updates to the current value of an attribute on the virtual instance once when it is mounted, and listens to updates on this attribute.
Give initialValue a type annotation if initializing to nil (e.g. vInstance:OutAttribute("Foo", nil :: string?))
OutInitialAttribute
Creates an Observable object that updates to the original value of an attribute on the virtual instance once when it is mounted. This is useful for referencing attributes on a premade instance without hardcoding them in a Dex component.
Give initialValue a type annotation if initializing to nil (e.g. vInst:OutInitialAttribute("Foo", nil :: string?))
OutInstance
Outputs a reference to the reconciled instance to a Dex State object.
DANGER
Using OutInstance to directly edit the rendered instance's properties may lead to unexpected behavior. It is recommended to only use OutInstance where no alternatives are available within the Dex API for your use case.
Use Case Alternatives
Preformating an instance
If an instance requires some amount of preformatting (e.g. deleting children of a premade template), you can use the directive VirtualInstance:DestroyPremadeChildren to destroy template UI elements within a design.
Most other use cases for preformatting can usually be handled through the :SetProperties directive.
Using a nested template for Dex.Clone
Dex.Clone supports passing in another VirtualInstance as an argument, as long as there are no circular dependencies. This will wait until the template VirtualInstance is mounted to instantiate the cloned instance.
OnMount/OnUnmount
The methods VirtualInstance:OnMount and VirtualInstance.OnUnmont allow side effects to be performed during the VirtualInstance's rendering lifecycle. If your use case does not require a direct reference to the rendered instance, this may be the best option.
AddChild
Adds a child VirtualInstance node to this VirtualInstance.
The path can be a dot-separated string, or an array of raw string names.
If the child is a VirtualInstance, it will be created or found depending on the type of VirtualInstance passed in.
AddChildren
VirtualInstance:
AddChildren
(
childMap:
CanBeObservable
<
{
[
any
]
:
CanBeObservable
<
VirtualInstance?
>
}
>
) →
(
)
Adds multiple children to the VirtualInstance given a child map. See VirtualInstance:Child() for API reference
MapChildren
VirtualInstance:
MapChildren
(
) →
(
)
Creates a child VirtualInstance for each key/value pair of the input
observable. When a Key/Value pair changes, the existing
VirtualInstance at that key is destroyed (if it exists), and a new one is
created (if the value is not nil
).
The mapping function should create a single VirtualInstance based on the current key and value.
This performs better than directly calling VirtualInstance:AddChildren with an Observable input, as this only creates/destroys virtual instances on the specific key/value pairs that have changed whenever the input observable updates.
Example:
local function ItemList()
local visibleItemIds = Dex.State({"Sword", "Gun", "MagicStaff"})
local scrollingFrame = Dex.New("ScrollingFrame", {
Size = UDim2.fromScale(1, 1)
})
scrollingFrame:MapChildren(visibleItemIds, function(i, id)
-- Instantiate a child ItemCard component for this given item ID,
-- and destroy/re-create a new item card when this ID changes.
return ItemCard({
id = id,
layoutOrder = i,
})
end)
return scollingFrame
end
MapChildrenByKey
VirtualInstance:
MapChildrenByKey
(
) →
(
)
Creates a child VirtualInstance for each key of the input observable. VirtualInstances will only be created/destroyed according to the provided mapping function when a new key is added or removed from the table.
The value is wrapped in an observable, and will forward updates to the mapping function when the value at a particlar key changes.
The mapping function should create a single VirtualInstance based on the current key and observable value at that key.
This is useful for instances where the input observable is a "Map", "Set", or "Dictionary" type, and may perform better than VirtualInstance:MapChildren in that case by minimizing creation/destruction of virtual instances.
Example:
local function ItemList()
local visibleItemIdSet = Dex.State({
["Sword"] = true,
["Gun"] = true,
["MagicStaff"] = true}
)
local scrollingFrame = Dex.New("ScrollingFrame", {
Size = UDim2.fromScale(1, 1)
})
scrollingFrame:MapChildrenByKey(visibleItemIds, function(id)
return ItemCard({
id = id,
})
end)
return scollingFrame
end
MapChildrenByValue
VirtualInstance:
MapChildrenByValue
(
) →
(
)
Creates a child VirtualInstance for each value of the input observable. VirtualInstances will only be created/destroyed according to the provided mapping function when a new value is added or removed from the table.
In the case of duplicate values, the last key/value pair defined in the input table will be considered.
The key is wrapped in an observable, and will forward updates to the mapping function if the value is moved to another key.
The mapping function should create a single VirtualInstance based on the current value and observable key for each value.
This is useful for instances where the input observable is an "Array" "Map", or "Dictionary" type, and may perform better than VirtualInstance:MapChildren in that case by minimizing creation/destruction of virtual instances.
Example:
local function ItemList()
local visibleItemIdSet = Dex.State({"Sword", "Gun", "MagicStaff"})
local scrollingFrame = Dex.New("ScrollingFrame", {
Size = UDim2.fromScale(1, 1)
})
scrollingFrame:MapChildrenByValue(visibleItemIds, function(id, i)
return ItemCard({
id = id,
layoutOrder = i, -- Observable<number>
})
end)
return scollingFrame
end
DestroyPremadeChildren
Adds a directive to destroy children of the VirtualInstance once it is mounted.
This is useful for scenarios where the design of a premade template contains objects that should be destroyed before rendering the actual UI.
The "filter" argument can be a name or predicate describing which children to destroy. If a name is defined, all children found with this name will be destroyed. If a predicate is defined, all children for which this function returns true will be destroyed.
OnMount
VirtualInstance:
OnMount
(
callback:
(
)
→
(
)
) →
(
)
Adds a lifecycle callback to be called when the Virtual Instance is mounted. This is useful for performing a side effect that begins/ends when a component starts/stops being rendered.
OnUnmount
VirtualInstance:
OnUnmount
(
callback:
(
)
→
(
)
) →
(
)
Adds a lifecycle callback to be called when the Virtual Instance is unmounted.
This is useful for performing a side effect that begins/ends when a component starts/stops being rendered.
FindChild
Creates a new VirtualInstance that automatically mounts on the child of the parent VirtualInstance. Will wait until a child with the given name is found.
The following blocks of code are equivalent:
local parent = Dex.Premade("Frame")
local child = Dex.Premade("Frame")
parent:AddChild("ChildName", child)
local parent = Dex.Premade("Frame")
local child = parent:FindChild("ChildName")
Combine
Combines the directives from another, or multiple other VirtualInstances. The VirtualInstances passed in must meet the following requirements:
- They must be of "Premade" type, with an equivalent or related ClassName to the target VirtualInstance.
- They must not already be rendered by Dex and/or combined with another VirtualInstance.
Combine can be used to compose different effects or input handlers within a VirtualInstance tree:
local function RecolorWithMouseInput()
local isHovering = Dex.State(false)
local isPressing = Dex.State(false)
return Dex.Premade("GuiButton", {
MouseEnter = function()
isHovering:Set(true)
end,
MouseLeave = function()
isHovering:Set(false)
end,
MouseButton1Down = function()
isPressing:Set(true)
end,
MouseButton1Up = function()
isPressing:Set(false)
end,
BackgroundColor3 = Dex.Map(isHovering, isPressing)(function(
currentHovering,
currentPressing
)
if currentPressing then
return Color3.fromHex("aaa")
elseif currentHovering then
return Color3.fromHex("ddd")
else
return Color3.fromHex("fff")
end,
end),
})
end
-- . . .
local function Button()
local button = Dex.New("Button", {
Activated = function()
print("Button was clicked!")
end,
AutoButtonColor = false,
})
button:Combine(RecolorWithMouseInput())
return button
end
SubscribeWhileMounted
VirtualInstance:
SubscribeWhileMounted
(
) →
(
)
Subscribes a custom listener to an observable's value while the VirtualInstance is mounted, and automatically unsubscribes the listener when the VirtualInstance unmounts.
TIP
SubscribeWhileMounted is much safer than calling Observable:Subscribe directly, as you do not need to handle an unsubscribe function manually to prevent memory leaks.
You can use this function to trigger side effects such as animations whenever an observable value changes:
local function PulseAnimation(valueToWatch: Dex.Observable<any>)
-- Create a UIScale object that plays a 1-second "pulse" animation
-- whenever a stopwatch is played.
local stopwatch = Dex.Stopwatch({duration = 1})
local uiScale = Dex.New("UIScale", {
Scale = stopwatch:Map(function(currentTime)
return 1 + (0.5 - currentTime) * 0.1
end)
})
-- Play the stopwatch whenever the valueToWatch observable changes.
uiScale:SubscribeWhileMounted(valueToWatch, function()
stopwatch:Play()
end)
return uiScale
end