Roact/React-Lua Hook Erroring Out When Using in ModuleScript

Hello Creators!

I am currently coding my User Interface in Roact/React-Lua. I’ve been testing it out and I’m planning on using it my in experiences.

But, when I tried to use Roact in my Module Script, I got the error ReplicatedStorage.ReactLua.node_modules.@jsdotlua.react.ReactHooks:113: attempt to index nil with 'useState' which sucks because this issue comes from the Roact ModuleScript.

I know that my version of Roact works because I’ve used it in my LocalScript and it worked.

Code

(Note: Not every useState is shown in the UI Elements)

So the error is on the “first” line of the function “function Profile:CreateUI(createUIProps)”. Basically, it’s where I defined local isProfileEnabled, setProfileEnabled = React.useState(true). But this error happens on any useState call.

Module Script:

--!strict

-- Variables: Services --
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

-- Variables: Local Player --
local LocalPlayer = Players.LocalPlayer
local PlayerGui = LocalPlayer.PlayerGui

-- Variables: React --
local React = require(ReplicatedStorage:WaitForChild("ReactLua"):WaitForChild("React"))
local RobloxReact = require(ReplicatedStorage:WaitForChild("ReactLua"):WaitForChild("ReactRoblox"))

-- Variables: Misc --
export type createUIProps = {
	DisplayName: string,
	Bio: string,
	Pronoun1: string,
	Pronoun2: string,
	IsPlayerVerified: boolean,
	IsPlayerDeveloper: boolean,
	IsPlayerViewingSelf: boolean,
	ProfilePicture: string,
}

local Profile = {}





-- [ Local Functions ] --



-- Create UIPadding --
local function createUIPadding(Properties: any)
	return React.createElement("UIPadding", {
		PaddingTop = Properties.PaddingTop or UDim.new(0, 0),
		PaddingLeft = Properties.PaddingLeft or UDim.new(0, 0),
		PaddingRight = Properties.PaddingRight or UDim.new(0, 0),
		PaddingBottom = Properties.PaddingBottom or UDim.new(0, 0),
	})
end



-- Create UIPadding --
local function createUICorner(Properties: any)
	return React.createElement("UIPadding", {
		CornerRadius = Properties.CornerRadius or UDim.new(0, 0),
	})
end



-- Create UIPadding --
local function createUIListLayout(Properties: any)
	return React.createElement("UIListLayout", {
		Padding = Properties.Padding or UDim.new(0, 10),
		FillDirection = Properties.FillDirection or Enum.FillDirection.Vertical,
		SortOrder = Properties.SortOrder or Enum.SortOrder.LayoutOrder,
		HorizontalAlignment = Properties.HorizontalAlignment or Enum.HorizontalAlignment.Left,
		VerticalAlignment = Properties.VerticalAlignment or Enum.VerticalAlignment.Top
	})
end





-- [ Functions ] --



-- createUI --
function Profile:CreateUI(createUIProps)

	-- Variables: useState --
	local isProfileEnabled, setProfileEnabled = React.useState(true)
	local isPronounsEditorEnabled, setPronounsEditorEnabled = React.useState(false)
	local pronounsEditorZIndex, setPronounsEditorZIndex = React.useState(1)
	local profileZIndex, setProfileZIndex = React.useState(2)

	local isEditMode, setEditMode = React.useState(false)

	local isPlayerVerified, setPlayerVerified = React.useState(false)
	local isPlayerDeveloper, setPlayerDeveloper = React.useState(false)
	local isPlayerViewingSelf, setPlayerViewingSelf = React.useState(false)

	local pronouns, setPronouns = React.useState({["Pronoun1"] = "", ["Pronoun2"] = "",})
	local bio, setBio = React.useState("")
	local pronounsEnabled, setPronounsEnabled = React.useState(false)
	local playerProfilePicture, setPlayerProfilePicture = React.useState("")
	local playerDisplayName, setPlayerDisplayName = React.useState("OnlyTwentyCharacters")

	-- Variables: Misc --
	local displayName = tostring(createUIProps.DisplayName)
	local isPronounsVisible = true
	local charCounterColor = #bio > 155 and Color3.new(0.74902, 0.172549, 0.172549) or Color3.new(0, 0, 0)



	-- Set Pronouns --
	if pronouns.Pronoun1 == "None" and pronouns.Pronoun2 == "None" then
		setPronounsEnabled(false)
	else
		setPronounsEnabled(true)
	end





	-- Create Elements --
	local profileFrame = isProfileEnabled and React.createElement("CanvasGroup", {
		Size = UDim2.new(0, 216, 0, 236),
		Position = UDim2.new(0.5, 0, 0.5, 0),
		BackgroundColor3 = Color3.new(0.988235, 0.937255, 0.937255),
		BorderSizePixel = 1,
		AnchorPoint = Vector2.new(0.5, 0.5),
		AutomaticSize = Enum.AutomaticSize.Y,
		ZIndex = profileZIndex,
		Name = "frmProfile",

		[React.Event.InputBegan] = function(object: Instance, inputObject: InputObject)
			setPronounsEditorZIndex(1)
			setProfileZIndex(2)
		end,
	},
	{
		-- User Inputs --
		dragDectector = React.createElement("UIDragDetector", {
			BoundingBehavior = Enum.UIDragDetectorBoundingBehavior.Automatic,
			DragRelativity = Enum.UIDragDetectorDragRelativity.Absolute,
			DragSpace = Enum.UIDragDetectorDragSpace.Parent,
			Enabled = true,
		}),

		-- Styling --
		uiStroke = React.createElement("UIStroke", {
			ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
			Color = Color3.new(0, 0, 0),
			LineJoinMode = Enum.LineJoinMode.Round,
			Thickness = 2,
			Transparency = 0.9
		}),

		uiPadding1 = React.createElement(createUIPadding, {PaddingBottom = UDim.new(0, 15)}),
		uiCorner1 = React.createElement(createUICorner, {CornerRadius = UDim.new(0, 15)}),

		-- Layout --
		uiListLayout1 = React.createElement(createUIListLayout, {
			Padding = UDim.new(0, 10),
			FillDirection = Enum.FillDirection.Vertical,
			SortOrder = Enum.SortOrder.LayoutOrder,
			HorizontalAlignment = Enum.HorizontalAlignment.Center,
			VerticalAlignment = Enum.VerticalAlignment.Top
		}),

		-- Header --
		topHeader = React.createElement("Frame", {
			Size = UDim2.new(1, 0, 0, 60),
			Position = UDim2.new(0, 0, 0, 0),
			BorderSizePixel = 0,
			BackgroundColor3 = Color3.new(0.321569, 0.611765, 0.929412),
			LayoutOrder = 1,
			BackgroundTransparency = 0,
		},
		{...}),

		-- Name And Pronouns --
		frmNamePronouns = React.createElement("Frame", {
			Size = UDim2.new(1, 0, 0, 0),
			BorderSizePixel = 0,
			BackgroundTransparency = 1,
			LayoutOrder = 2,
			AutomaticSize = Enum.AutomaticSize.Y
		}, {...}),

		-- Bio --
		defaultBio = not isEditMode and React.createElement("TextLabel", {
			Size = UDim2.new(1, 0, 0, 63),
			AutomaticSize = Enum.AutomaticSize.Y,
			FontFace = Font.new("rbxassetid://12187373327", Enum.FontWeight.Regular, Enum.FontStyle.Normal),
			TextSize = 12,
			LayoutOrder = 3,
			TextColor3 = Color3.new(0, 0, 0),
			BackgroundTransparency = 1,
			BorderSizePixel = 0,
			TextXAlignment = Enum.TextXAlignment.Left,
			TextYAlignment = Enum.TextYAlignment.Top,
			Text = tostring(bio),
			TextWrapped = true,
		}, {...}),

		editorBio = isEditMode and React.createElement("TextBox", {
			Size = UDim2.new(1, 0, 0, 63),
			AutomaticSize = Enum.AutomaticSize.Y,
			FontFace = Font.new("rbxassetid://12187373327", Enum.FontWeight.Regular, Enum.FontStyle.Normal),
			TextSize = 12,
			LayoutOrder = 3,
			TextColor3 = Color3.new(0, 0, 0),
			BackgroundTransparency = 1,
			BorderSizePixel = 0,
			TextXAlignment = Enum.TextXAlignment.Left,
			TextYAlignment = Enum.TextYAlignment.Top,
			Text = tostring(bio),
			TextWrapped = true,
			ClearTextOnFocus = false,

			[React.Event.Changed] = function(object: TextBox, property: string)
				if property == "Text" then
					setBio(tostring(object.Text))
				end
			end,
		}, {...}),

		bioCharCounter = isEditMode and React.createElement("TextLabel", {
			Size = UDim2.new(1, 0, 0, 10),
			FontFace = Font.new("rbxassetid://12187373327", Enum.FontWeight.Regular, Enum.FontStyle.Normal),
			TextSize = 10,
			LayoutOrder = 4,
			TextColor3 = charCounterColor,
			BackgroundTransparency = 1,
			BorderSizePixel = 0,
			TextXAlignment = Enum.TextXAlignment.Right,
			TextYAlignment = Enum.TextYAlignment.Center,
			Text = tostring(#bio).."/155",
			TextWrapped = false,
		}, {...}),


	})

	return {profileFrame}

end

return Profile

In this LocalScript, I’m defining and Rooting my User Interface. Previously when I used only a LocalScript to create my UI, it all worked. But it doesn’t seem to work when I use a ModuleScript.

LocalScript

-- Variables: Services --
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Variables: React Lua --
local React = require(ReplicatedStorage:WaitForChild("ReactLua"):WaitForChild("React"))
local RobloxReact = require(ReplicatedStorage:WaitForChild("ReactLua"):WaitForChild("ReactRoblox"))

-- Variables: Modules --
local module = require(ReplicatedStorage:WaitForChild("Modules"):WaitForChild("ReactUI"):WaitForChild("Profile"))

local ui = module:CreateUI()

local props = {
	DisplayName = "OnlyTwentyCharacters"
}

local handle = Instance.new("ScreenGui")
handle.Name = "React_ProfileGui"
handle.Parent = Players.LocalPlayer.PlayerGui
handle.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
local root = RobloxReact.createRoot(handle)

root:render(React.createElement(ui, props))

What I’ve Tried

I’m honestly stuck. At most, I’ve moved my lines of code around, but alas, nothing worked. I’m thinking this error might come from how I made my UI render??? But I have no idea.

I’m happy to answer any questions! Thanks in advance! :hidere:

You’re not alone, i got this bug while using StudioComponents’ TextInput Component
Did you find a fix?

Oh and also, i found out “resolveDispatcher” function return nil instead of returning the current dispatcher.

1 Like

I actually did find a fix, but to be honest it feels a little janky.

So, instead of doing:

root:render(React.createElement(module:CreateUI(), props))

I wrapped the module:CreateUI() function into a local function on the LocalScript and return the UI.

local function createProfileContents(Properties)
	local a = module:CreateProfile(Properties)
	return a
end

So my root:render looks like this now:

root:render(React.createElement(createProfileContents, props))

I’m honestly not sure why this works. I originally thought it was an issue with the props/Properties but because it was React throwing a Hook error makes me think otherwise.

If there’s a React/Roact Veteran who knows why React is throwing a Hook error, please let us know!

Anyways, I hope this helps! I’m happy to answer any other questions.

This should obviously be UICorner, not UIPadding.

Just a heads up, I think for questions ergarding Roact/React-Lua you should ask them in the Roblox Open Source Community discord channel, they have a dedicated thread for Roact and React Lua.

1 Like

Unfortunately, it doesn’t work for me. Thank you for responding, tho

You’re right, i’m already on OSS Community server but never actually look into all the channels

You might be running into this. Set _G.__DEV__ = true before you require react and see if the error appears.

1 Like

Well i found the problem (im not using react-lua at all so i didn’t know about this).
You need to create a local function and then execute it by creating an element, it should look like this:

local function Page()
	local value, setValue = React.useState(1)
	-- TODO
end

local page = React.createElement(Page)

Helped a lot. Found the solution from a certain Nidoxs who said

“You can only use hooks in function components”

Hope it helps people !

you were actually right but i didn’t do the right thing you did

root:render(React.createElement(Page(), props))

I shouldn’t have executed Page()

It gived this warning:

Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

It would have helped me a lot !

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.