diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cea63a..5075deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/Runtime.lua b/src/Runtime.lua index 6568597..dc2e9c6 100644 --- a/src/Runtime.lua +++ b/src/Runtime.lua @@ -31,6 +31,8 @@ type StackFrame = { discriminator: string | number, } +export type RefTable = { [string]: Instance } + local stack: { StackFrame } = {} local recentErrors = {} @@ -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() @@ -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 @@ -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] diff --git a/src/init.lua b/src/init.lua index 6398906..7210611 100644 --- a/src/init.lua +++ b/src/init.lua @@ -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, diff --git a/tests/plasma.spec.lua b/tests/plasma.spec.lua index c71b764..10016f5 100644 --- a/tests/plasma.spec.lua +++ b/tests/plasma.spec.lua @@ -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() @@ -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) @@ -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