Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add useRefs hook #17

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ The format is based on [Keep a Changelog][kac], and this project adheres to

## [Unreleased]

### Added
- Added `useRefs`

## [0.4.3] - 2024-01-31

### Added
Expand Down
26 changes: 25 additions & 1 deletion src/Runtime.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type StackFrame = {
discriminator: string | number,
}

export type RefTable = { [string]: Instance }

local stack: { StackFrame } = {}

local recentErrors = {}
Expand Down Expand Up @@ -269,7 +271,7 @@ end
`useInstance` returns the `ref` table that is passed to it. You can use this to create references to objects
you want to update in the widget body.
]=]
function Runtime.useInstance(creator: () -> Instance): Instance
function Runtime.useInstance(creator: (ref: RefTable) -> Instance): Instance
local node = stack[#stack].node
local parentFrame = Runtime.nearestStackFrameWithInstance()

Expand All @@ -279,6 +281,8 @@ function Runtime.useInstance(creator: () -> Instance): Instance
node.refs = {}
local instance, container = creator(node.refs)

table.freeze(node.refs)

if instance ~= nil then
instance.Parent = parent
node.instance = instance
Expand All @@ -297,6 +301,26 @@ function Runtime.useInstance(creator: () -> Instance): Instance
return node.refs
end

--[=[
@within Plasma
@return { [string]: Instance } -- Returns the `ref` table
@tag hooks

Returns the `ref` table that is attached to the current `useInstance` call.

This hook can only be used inside `useInstance` and will error if done otherwise. You can use this instead
of the `ref` parameter if you're using nested functions that returns instances and don't want
to pass the table to a descendant via prop drilling.
]=]
function Runtime.useRefs(): RefTable
local node = stack[#stack].node
if node.refs == nil or table.isfrozen(node.refs) then
error("Runtime.useRefs cannot be used outside Runtime.useInstance", 2)
end

return node.refs
end

function Runtime.nearestStackFrameWithInstance(): StackFrame?
for i = #stack - 1, 1, -1 do
local frame = stack[i]
Expand Down
1 change: 1 addition & 0 deletions src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ return {
widget = Runtime.widget,
useState = Runtime.useState,
useInstance = Runtime.useInstance,
useRefs = Runtime.useRefs,
useEffect = Runtime.useEffect,
useKey = Runtime.useKey,
setEventCallback = Runtime.setEventCallback,
Expand Down
77 changes: 75 additions & 2 deletions tests/plasma.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ return function()
describe("plasma", function()
it("should create and destroy things", function()
local folder = Instance.new("Folder")

local root = Plasma.new(folder)

Plasma.start(root, function()
Expand All @@ -21,7 +20,6 @@ return function()

it("should create and destroy from a single start point", function()
local folder = Instance.new("Folder")

local root = Plasma.new(folder)

local function start(visible)
Expand All @@ -39,5 +37,80 @@ return function()

expect(folder:FindFirstChildWhichIsA("TextButton")).to.never.be.ok()
end)

it("should support `useRefs` hook", function()
local folder = Instance.new("Folder")
local root = Plasma.new(folder)

local refsTable

local function DescendantComponent()
return Plasma.create("Frame", {
[Plasma.useRefs()] = "baz",
BackgroundColor3 = Color3.fromHex("#00f"),
})
end

local function AncestorComponent()
local ref = Plasma.useRefs()
refsTable = ref

return Plasma.create("Frame", {
[ref] = "foo",
BackgroundColor3 = Color3.fromHex("#f00"),
-- children
Plasma.create("Frame", {
BackgroundColor3 = Color3.fromHex("#0f0"),
-- children
DescendantComponent(),
}),
})
end

local frame = Plasma.beginFrame(root, function(tbl)
expect(Plasma.useInstance(AncestorComponent)).to.equal(tbl)
end, refsTable)

Plasma.continueFrame(frame, function(refs, red, blue)
expect(refs.foo).to.be.ok()
expect(refs.bar).to.never.be.ok()
expect(refs.baz).to.be.ok()

expect(refs.foo.BackgroundColor3).to.equal(red)
expect(refs.baz.BackgroundColor3).to.equal(blue)
end, refsTable, Color3.fromHex("#f00"), Color3.fromHex("#00f"))

Plasma.finishFrame(root)
end)

it("should disallow `useRefs` outside `useInstance`", function()
local folder = Instance.new("Folder")
local root = Plasma.new(folder)

Plasma.start(root, function()
Plasma.useInstance(function()
return Plasma.create("Folder")
end)

expect(Plasma.useRefs).to.throw()
end)
end)

it("should disallow mutating the returned refs table", function()
local folder = Instance.new("Folder")
local root = Plasma.new(folder)

local refs

Plasma.start(root, function()
refs = Plasma.useInstance(function(ref)
return Plasma.create("Folder", { [ref] = "test" })
end)
end)

expect(function()
refs.test = true
end).to.throw()
end)
end)
end
Loading