Dynamic Scoping
Dynamic scoping is the act of creating and destroying new scopes in response to source updates. This is needed for conditionally rendering parts of your UI, such as opening and closing menus.
While Vide provides functions for common ways to do this, this section will show how you can implement them yourself so you are not limited by only what is provided.
Recreating show()
The most basic one, show()
, can be implemented yourself like so:
local function show(toggle: () -> unknown, component: () -> Instance)
return derive(function()
return if toggle() then untrack(component) else nil
end)
end
The main thing to note here is the use of untrack()
. This function runs its callback in a new stable scope. Without this, if the component were to create a reactive scope, an error would occur since a reactive scope cannot be created within a reactive scope.
You can see from the above graph how the effect would not be created directly inside the derive, there is a stable scope between them. This requirement exists as a guard against unintentional rerendering of UI.
Recreating switch()
local function switch(key)
return function(map)
return derive(function()
local component = map[key()]
return if component then untrack(component) else nil
end)
end
end
Recreating indexes()
This is a more complicated function because it manages multiple scopes at the same time, unlike the previous functions. Because some scopes may persist between reruns, we cannot use untrack()
anymore which automatically destroys on rerun; we must use root()
where the lifetime of each scope is managed manually and independently.
local function indexes<I, VI, VO>(
input: () -> Map<I, VI>,
transform: (value: () -> VI, index: I) -> VO
)
local index_caches = {} :: Map<I, {
previous_input: VI,
output: VO,
source: (VI) -> VI,
destroy: () -> ()
}?>
-- destroy all scopes if the parent scope is destroyed
cleanup(function()
for _, cache in index_caches do
assert(cache).destroy()
end
end)
return derive(function()
local new_input = input()
-- destroy scopes of removed indexes
for i, cache in index_caches do
if new_input[i] == nil then
assert(cache).destroy()
index_caches[i] = nil
end
end
-- create scopes or update sources of added or changed index values
for i, v in new_input do
local cache = index_caches[i]
if cache == nil then -- no scope created for this index, create one
local src = source(v)
local destroy, result = root(function()
return transform(src, i)
end)
index_caches[i] = {
destroy = destroy,
source = src,
output = result,
previous_input = v
}
elseif cache.previous_input ~= v then -- scope exists, update source
cache.previous_input = v
cache.source(v)
else -- scope exists and value has not changed; do nothing
end
end
-- return the cached output values as an array
local array = table.create(#index_caches)
for _, cache in index_caches do
table.insert(array, assert(cache).output)
end
return array
end)
end
Though the above functions are already provided to you by Vide, this serves as an example for how you may create your own dynamic scope functions.