From fc2d02e96e8a241bcebbf208f436e7391f572242 Mon Sep 17 00:00:00 2001 From: Rimuy Date: Sat, 10 Feb 2024 04:36:53 -0300 Subject: [PATCH 1/6] Add `useRefs` --- CHANGELOG.md | 3 +++ src/Runtime.lua | 24 +++++++++++++++++++++++- src/init.lua | 1 + 3 files changed, 27 insertions(+), 1 deletion(-) 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..550ed25 100644 --- a/src/Runtime.lua +++ b/src/Runtime.lua @@ -31,6 +31,8 @@ type StackFrame = { discriminator: string | number, } +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,24 @@ 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. +]=] +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, From 960dc9b52f7bcb51a59ca3ae100897565d5c5db6 Mon Sep 17 00:00:00 2001 From: rimuy <46044567+rimuy@users.noreply.github.com> Date: Sat, 10 Feb 2024 04:57:26 -0300 Subject: [PATCH 2/6] Update Runtime.lua --- src/Runtime.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Runtime.lua b/src/Runtime.lua index 550ed25..1710d6b 100644 --- a/src/Runtime.lua +++ b/src/Runtime.lua @@ -308,7 +308,9 @@ end 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. + 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 From 4f8848100d5fa5e645c7f69de190baadaf580b2e Mon Sep 17 00:00:00 2001 From: Rimuy Date: Sat, 10 Feb 2024 15:41:35 -0300 Subject: [PATCH 3/6] Add unit testing --- tests/plasma.spec.lua | 77 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/tests/plasma.spec.lua b/tests/plasma.spec.lua index c71b764..3945f6a 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"), + + Plasma.create("Frame", { + BackgroundColor3 = Color3.fromHex("#0f0"), + + 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 From a4857ed46d938088340670240b719cc290d3328b Mon Sep 17 00:00:00 2001 From: Rimuy Date: Sat, 10 Feb 2024 15:43:29 -0300 Subject: [PATCH 4/6] Make formatter happy --- tests/plasma.spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/plasma.spec.lua b/tests/plasma.spec.lua index 3945f6a..483d053 100644 --- a/tests/plasma.spec.lua +++ b/tests/plasma.spec.lua @@ -58,10 +58,10 @@ return function() return Plasma.create("Frame", { [ref] = "foo", BackgroundColor3 = Color3.fromHex("#f00"), - + -- children Plasma.create("Frame", { BackgroundColor3 = Color3.fromHex("#0f0"), - + -- children DescendantComponent(), }) }) From 617260f6734dec4ea58a611abcbcbf6242f94c3d Mon Sep 17 00:00:00 2001 From: Rimuy Date: Sat, 10 Feb 2024 15:44:38 -0300 Subject: [PATCH 5/6] Add trailing comma --- tests/plasma.spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/plasma.spec.lua b/tests/plasma.spec.lua index 483d053..10016f5 100644 --- a/tests/plasma.spec.lua +++ b/tests/plasma.spec.lua @@ -63,7 +63,7 @@ return function() BackgroundColor3 = Color3.fromHex("#0f0"), -- children DescendantComponent(), - }) + }), }) end From eee1ef5ff0cb4e5629ec5e0b8bcec3826b39fa53 Mon Sep 17 00:00:00 2001 From: rimuy <46044567+rimuy@users.noreply.github.com> Date: Sat, 10 Feb 2024 23:21:31 -0300 Subject: [PATCH 6/6] Update Runtime.lua --- src/Runtime.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Runtime.lua b/src/Runtime.lua index 1710d6b..dc2e9c6 100644 --- a/src/Runtime.lua +++ b/src/Runtime.lua @@ -31,7 +31,7 @@ type StackFrame = { discriminator: string | number, } -type RefTable = { [string]: Instance } +export type RefTable = { [string]: Instance } local stack: { StackFrame } = {}