Frames not loading from given tables

Frames are not being added and the console prints “Unable to assign property Text. string expected, got nil”, in line 25, LocalScript:

Datamod

local module = {}
-- DataModule

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataStoreService = game:GetService("DataStoreService")
local Datastore = DataStoreService:GetDataStore("PlayerSettings")

local Events = ReplicatedStorage:FindFirstChild("Events")
local Remotes = Events:FindFirstChild("Remotes")
local Folder = Remotes:FindFirstChild("SetsRemotes")

local SettingsModule = require(script.Parent.Settings)
function retry(operationFunc, max)
	local retriesLeft = max
	local success, result
	repeat
		success, result = pcall(operationFunc)
		if not success then
			retriesLeft -= 1
			task.wait(1)
		end
	until success or retriesLeft <= 0

	return success, result
end

module.loadPlayerSettings = function(player: Player)
	local success, settings = retry(function()
		return Datastore:GetAsync(player.UserId)
	end, 3)

	if success and settings then
		Folder["LoadEvent"]:FireClient(player, settings)
	else
		local defaultSettings = SettingsModule.GetSettings()
		Folder["LoadEvent"]:FireClient(player, defaultSettings)
		
		warn("No settings after retries, loading default", player.Name)
	end
end

module.savePlayerSettings = function(player: Player, settings: table)
	local success, result = retry(function()
		return Datastore:SetAsync(player.UserId, settings)
	end, 3)

	if not success then
		warn("Error saving after retries, result:", result)
	end
end

return module```

Client
```lua
-- UI localscript
local PlayersService = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local LocalPlayer = PlayersService.LocalPlayer

local Events = ReplicatedStorage:FindFirstChild("Events")
local Remotes = Events:FindFirstChild("Remotes")
local Folder = Remotes:FindFirstChild("SetsRemotes")

local UI = script.Parent.Parent
local SettingsFrame = UI:FindFirstChild("SettingsFrame", true)
local ClippingFrame = SettingsFrame.Pages
local Template = ClippingFrame.Template

Folder.LoadEvent.OnClientEvent:Connect(function(settings)
	for i, setting in pairs{settings} do
		if typeof(settings) ~= "table" then
			warn("Invalid settings received:", settings)
			return
		end
		print(i, setting)

		local Frame = Template:Clone()
		Frame.sName.Text = setting.Title
		Frame.LayoutOrder = i
		
		if setting.Class == "Player" then
			Frame.Parent = ClippingFrame.scroll_Plr
		elseif setting.Class == "Game" then
			Frame.Parent = ClippingFrame.scroll_Game
		end

		if setting.Type == "Toggle" then
			local inputFrame = Frame.toggle
			inputFrame.Visible = true

			local togBtn = inputFrame.toggleBtn
			local on = false
			togBtn.MouseButton1Click:Connect(function()
				if on == false then
					on = true
					togBtn.BackgroundColor3 = Color3.fromRGB(65, 255, 51)

					local image = togBtn.Icon
					image.Image = image:GetAttribute("on")
					image:TweenPosition(UDim2.fromScale(1,0), "InOut", "Sine", 0.15, true)

					Folder.UpdateEvent:FireServer(LocalPlayer, setting.Title, setting.On)
				else
					on = false
					togBtn.BackgroundColor3 = Color3.fromRGB(255, 37, 37)
					local image = togBtn.Icon
					image.Image = image:GetAttribute("off")
					image:TweenPosition(UDim2.fromScale(0,0), "InOut", "Sine", 0.15, true)

					Folder.UpdateEvent:FireServer(LocalPlayer, setting.Title, setting.Off)
				end
			end)
			
		elseif setting.Type == "Field" then
			local inputFrame = Frame.field
			inputFrame.Visible = true
			inputFrame.TextBox.Text = setting.Default

			inputFrame.TextBox.FocusLost:Connect(function()
				local value = inputFrame.TextBox.Text
				Folder.UpdateEvent:FireServer(LocalPlayer, setting["Title"], value)
			end)
		end
	end
end)
3 Likes

The error is saying that setting.Title is nil

I suspect the issue is from this line:

for i, setting in pairs{settings} do

I believe you meant to do

for i, setting in pairs(settings) do

which could be simplified to just

for i, setting in settings do

Helped fix one problem, but the main one still continues,

For some reason the output keeps saying it’s a string value “attempt to iterate over a string value”. Which was a problem I had a few hours ago which is why I used curly brackets instead. I think it may have a problem to do with the module that actually holds all settings.

module.GetSettings = function()
	return {
		[1] = {
			Title = "Custom Death Sound",
			Type = "Field",
			Default = "",
			Class = "Game",
		},
-- (yada yada)

Can you please send the updated scripts, plus the line number that is erroring?

Gotcha
(Line 17 in localscript - attempt to iterate over a string value)

client

-- UI localscript
local PlayersService = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local LocalPlayer = PlayersService.LocalPlayer

local Events = ReplicatedStorage:FindFirstChild("Events")
local Remotes = Events:FindFirstChild("Remotes")
local Folder = Remotes:FindFirstChild("SetsRemotes")

local UI = script.Parent.Parent
local SettingsFrame = UI:FindFirstChild("SettingsFrame", true)
local ClippingFrame = SettingsFrame.Pages
local Template = script.Template

Folder.LoadEvent.OnClientEvent:Connect(function(returnedSettings)
	for i, setting in returnedSettings do
		print(i, setting)

		local Frame = Template:Clone()
		Frame.sName.Text = setting.Title
		Frame.LayoutOrder = i
		
		if setting.Class == "Player" then
			Frame.Parent = ClippingFrame.scroll_Plr
		elseif setting.Class == "Game" then
			Frame.Parent = ClippingFrame.scroll_Game
		end

		if setting.Type == "Toggle" then
			local inputFrame = Frame.toggle
			inputFrame.Visible = true

			local togBtn = inputFrame.toggleBtn
			local on = false
			togBtn.MouseButton1Click:Connect(function()
				if on == false then
					on = true
					togBtn.BackgroundColor3 = Color3.fromRGB(65, 255, 51)

					local image = togBtn.Icon
					image.Image = image:GetAttribute("on")
					image:TweenPosition(UDim2.fromScale(1,0), "InOut", "Sine", 0.15, true)

					Folder.UpdateEvent:FireServer(LocalPlayer, setting.Title, setting.On)
				else
					on = false
					togBtn.BackgroundColor3 = Color3.fromRGB(255, 37, 37)
					local image = togBtn.Icon
					image.Image = image:GetAttribute("off")
					image:TweenPosition(UDim2.fromScale(0,0), "InOut", "Sine", 0.15, true)

					Folder.UpdateEvent:FireServer(LocalPlayer, setting.Title, setting.Off)
				end
			end)
			
		elseif setting.Type == "Field" then
			local inputFrame = Frame.field
			inputFrame.Visible = true
			inputFrame.TextBox.Text = setting.Default

			inputFrame.TextBox.FocusLost:Connect(function()
				local value = inputFrame.TextBox.Text
				Folder.UpdateEvent:FireServer(LocalPlayer, setting["Title"], value)
			end)
		end
	end
end)

module(not updated)

local module = {}
-- DataModule

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataStoreService = game:GetService("DataStoreService")
local Datastore = DataStoreService:GetDataStore("PlayerSettings")

local Events = ReplicatedStorage:FindFirstChild("Events")
local Remotes = Events:FindFirstChild("Remotes")
local Folder = Remotes:FindFirstChild("SetsRemotes")

local SettingsModule = require(script.Parent.Settings)
function retry(operationFunc, max)
	local retriesLeft = max
	local success, result
	repeat
		success, result = pcall(operationFunc)
		if not success then
			retriesLeft -= 1
			task.wait(1)
		end
	until success or retriesLeft <= 0

	return success, result
end

module.loadPlayerSettings = function(player: Player)
	local success, settings = retry(function()
		return Datastore:GetAsync(player.UserId)
	end, 3)

	if success and settings then
		Folder["LoadEvent"]:FireClient(player, settings)
	else
		local defaultSettings = SettingsModule.GetSettings()
		Folder["LoadEvent"]:FireClient(player, defaultSettings)
		
		warn("No settings after retries, loading default", player.Name)
	end
end

module.savePlayerSettings = function(player: Player, settings: table)
	local success, result = retry(function()
		return Datastore:SetAsync(player.UserId, settings)
	end, 3)

	if not success then
		warn("Error saving after retries, result:", result)
	end
end

return module

Is the “No settings after retries” thing being outputted?

If not, can you please (temporarily) add and false to the if success and settings then line of the module script
(so it becomes

	if success and settings and false then

). This makes it always load the default data, so we can test if it’s the saved data that is now wrong

Worked the first time I tested, though when I joined back it stopped working. While it was working it did load the default data only so that’s nice
image

I think there may be an issue in your saving script, which means it’s saving e.g. just the title of the setting, rather than the table of setting information. Can you please double check if that’s the case?

(If you want to load your saved data again, just remove the and false that I had you add before)

Yeah, well now the datastore module is dependent on the “and false” thing. I’m pretty sure it’s having trouble saving/loading.

Forgot to add - heres the server script which may be related

-- init Script
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Folder = ReplicatedStorage:WaitForChild("Events").Remotes.SetsRemotes
local SettingsModule = require(script.Settings)
local DataModule = require(script.DataModule)

local playerSettingsCache = {}

Folder.UpdateEvent.OnServerEvent:Connect(function(player, key, value)
	local playerSettings = playerSettingsCache[player.UserId] or {}
	local isValid = false
	for _, setting in SettingsModule.GetSettings() do
		if setting.Title == key then
			isValid = true
			break
		end
	end
	
	if not isValid then return end
	playerSettings[key] = value
	playerSettingsCache[player.UserId] = playerSettings
	DataModule.savePlayerSettings(player, playerSettings)
end)

--load cache on join
Players.PlayerAdded:Connect(function(player)
	DataModule.loadPlayerSettings(player)
end)

--save cache on leave
Players.PlayerRemoving:Connect(function(player)
	if playerSettingsCache[player.UserId] then
		DataModule.savePlayerSettings(player, playerSettingsCache[player.UserId])
	end
end)

game:BindToClose(function()
	for _, player in ipairs(Players:GetPlayers()) do
		if playerSettingsCache[player.UserId] then
			DataModule.savePlayerSettings(player, playerSettingsCache[player.UserId])
		end
	end
end)

The loading side should be fine - it’s the saving side that isn’t working correctly.

If you put back the and false bit, and then add print(settings) within the savePlayerSettings function, what does it output?

(you missed a ` in your edit btw)

Doesn’t print anything since it doesn’t save, it does return the no settings after retries warn though