Attempt to index nil with SetAsync

I’m trying to make a twitter codes system in my game, and here’s the main script:

local DSS = game:GetService("DataStoreService")
local TCODESDS = DSS:GetDataStore("CodesDataStore")

local codes = {
	["r3l3453"] = 1500
}

local asyncCodes = {
	["r3l3453"] = false
}

game.ReplicatedStorage.RedeemCode.OnServerInvoke = function(player, codeValue)
	codeValue = string.lower(tostring(codeValue))
	local setAsyncCodes = {}
	
	local playerUserId = player.UserId
	
	local CodesData
	local success, errormessage = pcall(function()
		CodesData = TCODESDS:GetAsync(playerUserId)
	end)
	
	if CodesData ~= nil then
		if table.find(CodesData, tostring(codeValue)) then
			local codeValue2 = table.find(CodesData, tostring(codeValue))
			
			if codeValue2 == false then
				for _, v in pairs(CodesData) do
					if v ~= playerUserId then
						table.insert(setAsyncCodes, v)
					end
				end
				if table.find(setAsyncCodes, tostring(codeValue)) then
					setAsyncCodes[codeValue] = true
				end
				wait(0.5)
				if table.find(setAsyncCodes, tostring(codeValue)) then
					if setAsyncCodes[codeValue] == true then
						CodesData:SetAsync(playerUserId, setAsyncCodes)
					end
				end
				if table.find(codes, codeValue) then
					player:WaitForChild("leaderstats")
					player.leaderstats.Coins.Value = player.leaderstats.Coins.Value + tonumber(codes[codeValue])
					return true
				end
			elseif codeValue2 == true then
				return false
			end
		end
	elseif CodesData == nil then
		CodesData:SetAsync(playerUserId, asyncCodes)
		if table.find(CodesData, tostring(codeValue)) then
			local codeValue2 = table.find(CodesData, tostring(codeValue))

			if codeValue2 == false then
				for _, v in pairs(CodesData) do
					if v ~= playerUserId then
						table.insert(setAsyncCodes, v)
					end
				end
				if table.find(setAsyncCodes, tostring(codeValue)) then
					setAsyncCodes[codeValue] = true
				end
				wait(0.5)
				if table.find(setAsyncCodes, tostring(codeValue)) then
					if setAsyncCodes[codeValue] == true then
						CodesData:SetAsync(playerUserId, setAsyncCodes)
					end
				end
				if table.find(codes, codeValue) then
					player:WaitForChild("leaderstats")
					player.leaderstats.Coins.Value = player.leaderstats.Coins.Value + tonumber(codes[codeValue])
					return true
				end
			elseif codeValue2 == true then
				return false
			end
		end
	end
end

For some reason, line 52 (the line under if codesdata == nil then) is giving me an error:
image
Why is this?

btw the redeem button script is:

local db = false

script.Parent.Activated:Connect(function()
	if db == false then
		local returnedValue = game.ReplicatedStorage.RedeemCode:InvokeServer(script.Parent.Text)
		db = true
		wait(0.05)
		if returnedValue == true then
			script.Parent.Text = "Success!"
		elseif returnedValue == false then
			script.Parent.Text = "Not valid"
		end
		wait(3)
		db = false
		script.Parent.Text = "REDEEM"
	end
end)

Do you mean:

elseif CodesData then

Sidenote, but do not save data when the stats change, but rather when the player leaves and on a set interval(autosave.)

“Attempt to index nil with SetAsync” means that the thing that you are trying to set the async for doesn’t exist. Your elseif statement

elseif CodesData == nil then

will only run if CodesData doesn’t exist. So then, how do you set the async for something that doesn’t exist?
You also use

local success, errormessage = pcall(function()
  CodesData = TCODESDS:GetAsync(playerUserId)
end)

to define CodesData, but if the player doesn’t have a datastore in TCODESDS then you will get nil. Since you are using a pcall, you could try

print(errormessage)

after the end) for the pcall to see what’s potentially causing the error.

You wouldn’t be calling :SetAsync() on CodesData in the first place. :SetAsync() should be used with the TCODESDS variable, since it’s the actual data store. CodesData is just the data that was saved, it doesn’t have any functions.

@BaldisFriend Change the bits where you put “CodesData:SetAsync” to “TCODESDS:SetAsync” instead.

1 Like

It worked… but:

You have this:

	elseif CodesData == nil then
		TCODESDS:SetAsync(playerUserId, asyncCodes)
		if table.find(CodesData, tostring(codeValue)) then

CodesData is nil, so you’re basically doing table.find(nil, value), which just won’t work. It looks like the whole block of code after that if statement is relying on CodesData existing, even though it wouldn’t be running unless CodesData was nil.

Click to view rewritten code
local DataStoreService = game:GetService("DataStoreService");
local CodeStore = DataStoreService:GetDataStore("CodesDataStore");

local ReplicatedStorage = game:GetService("ReplicatedStorage");
local RedeemCode = ReplicatedStorage:WaitForChild("RedeemCode", 10);

local codes = {
	r3l3453 = 1500;
};

local pendingChanges = {};

RedeemCode.OnServerInvoke = function(player, value)
	if pendingChanges[player] then
		return
	end
	
	local userId = player.UserId;

	local success, result = pcall(function()
		return CodeStore:GetAsync(userId);
	end)

	if success then
		local existing = typeof(result)=="table" and result[value];
		if existing then
			warn(player, "already redeemed code:", value);
			pendingChanges[player] = nil;
			return false;
		end

		local codeValue = codes[value];

		if codeValue then
			local leaderstats = player:FindFirstChild("leaderstats");
			local coins = leaderstats and leaderstats:FindFirstChild("Coins");

			if coins then 
				coins.Value += codeValue;
			end

			CodeStore:UpdateAsync(userId, function(data)
				data = data or {};
				data[value] = true;
				
				pendingChanges[player] = nil;

				return data;
			end)

			return true;
		else
			warn(player, "attempted to redeem an invalid code:", value);
		end
	else
		warn(result);
	end
	
	pendingChanges[player] = nil;

	return false;
end
1 Like

Alright, but it comes up on the button to always say “Not valid”

edit: It’s not creating data when you use it?

What does the output say? I’ve already tested it and it worked - you probably already have data stored for that code from your tests, so it gets rejected because it was already redeemed.

Edit: After looking at your first code attempt, I think you have a big of a misunderstanding about the logic here. You wouldn’t want to re-apply the code increment to their coins value every time they join the game, which is what it looks like you were attempting towards the bottom of your code - then they could just constantly leave and rejoin for free money (it’d add every time they rejoin), assuming that you’re saving their leaderstats elsewhere.

1 Like

I accidentally put the redeem button’s text, instead of the code box’s text into the code.

However, it’s not creating any data so you can spam the codes, even if you rejoin.

I tried creating data, but I can’t.

This is what I have:

--[[

local DSS = game:GetService("DataStoreService")
local TCODESDS = DSS:GetDataStore("CodesDataStore")

local codes = {
	["r3l3453"] = 1500
}

local asyncCodes = {
	["r3l3453"] = false
}

game.ReplicatedStorage.RedeemCode.OnServerInvoke = function(player, codeValue)
	codeValue = string.lower(tostring(codeValue))
	local setAsyncCodes = {}

	local playerUserId = player.UserId

	local CodesData
	local success, errormessage = pcall(function()
		CodesData = TCODESDS:GetAsync(playerUserId)
	end)

	if CodesData ~= nil then
		if table.find(CodesData, tostring(codeValue)) then
			local codeValue2 = table.find(CodesData, tostring(codeValue))

			if codeValue2 == false then
				for _, v in pairs(CodesData) do
					if v ~= playerUserId then
						table.insert(setAsyncCodes, v)
					end
				end
				if table.find(setAsyncCodes, tostring(codeValue)) then
					setAsyncCodes[codeValue] = true
				end
				wait(0.5)
				if table.find(setAsyncCodes, tostring(codeValue)) then
					if setAsyncCodes[codeValue] == true then
						CodesData:SetAsync(playerUserId, setAsyncCodes)
					end
				end
				if table.find(codes, codeValue) then
					player:WaitForChild("leaderstats")
					player.leaderstats.Coins.Value = player.leaderstats.Coins.Value + tonumber(codes[codeValue])
					return true
				end
			elseif codeValue2 == true then
				return false
			end
		end
	elseif CodesData == nil then
		TCODESDS:SetAsync(playerUserId, asyncCodes)
		local codeValue2 = table.find(CodesData, tostring(codeValue))

		if codeValue2 == false then
			for _, v in pairs(CodesData) do
				if v ~= playerUserId then
					table.insert(setAsyncCodes, v)
				end
			end
			if table.find(setAsyncCodes, tostring(codeValue)) then
				setAsyncCodes[codeValue] = true
			end
			wait(0.5)
			if table.find(setAsyncCodes, tostring(codeValue)) then
				if setAsyncCodes[codeValue] == true then
					CodesData:SetAsync(playerUserId, setAsyncCodes)
				end
			end
			if table.find(codes, codeValue) then
				player:WaitForChild("leaderstats")
				player.leaderstats.Coins.Value = player.leaderstats.Coins.Value + tonumber(codes[codeValue])
				return true
			end
		elseif codeValue2 == true then
			return false
		end
	end
end

--]]

-- Fixed Code:

local DataStoreService = game:GetService("DataStoreService");
local CodeStore = DataStoreService:GetDataStore("CodesDataStore");

local ReplicatedStorage = game:GetService("ReplicatedStorage");
local RedeemCode = ReplicatedStorage:WaitForChild("RedeemCode", 10);

local codes = {
	r3l3453 = 1500;
};

RedeemCode.OnServerInvoke = function(player, value)
	local userId = player.UserId;

	local success, result = pcall(function()
		CodeStore:GetAsync(userId);
	end)

	if success then
		local existing = typeof(result)=="table" and result[value];
		if existing then
			warn(player, "already redeemed code:", value);
			return false;
		end

		local codeValue = codes[string.lower(value)];

		if codeValue then
			local leaderstats = player:FindFirstChild("leaderstats");
			local coins = leaderstats and leaderstats:FindFirstChild("Coins");

			if coins then 
				coins.Value += codeValue;
			end

			CodeStore:UpdateAsync(userId, function(data)
				data = data or {};
				data[value] = true;
			end)

			return true;
		else
			warn(player, "attempted to redeem an invalid code:", value);
			return false;
		end
	else
		warn(result);
	end

	return false;
end

It seems to be saving data fine for me, if you mean storing whether or not a code has been previously used. Leaderstats aren’t being saved in this code because I assume you’re doing that elsewhere, since there wasn’t an attempt to save the actual coins value in the first version of the code.

Here’s an example place that I set up with the same code:

Edit: Except for the local script, which I’m only invoking the server with and don’t have any text changes for whether it was a success or not.

Here’s the dev console showing my saved data from random codes that I tested:

1 Like

Ah, I looked through your edit of my code again.

Your edit:
image

You for some reason removed my “return” here when you were copying it.

My post:
image

The return is necessary, or else result is nil.

Edit: You also removed my return inside of the UpdateAsync function for some reason? Is there a reason why you think you need to remove these? UpdateAsync requires you to return the data that you want saved… or else there’s nothing to save.

Your edit:
image

My post:
image

1 Like

I thought it would’ve returned a different value so the button would say “Invalid”

No. They’re in a different scope. It won’t skip the rest of the script. If you have a pcall and don’t return anything, then there will be no result to read.

For example:

local function example()
	local success, result = pcall(function()
		return 4;
	end)
	
	print(result); -- prints 4
	return 5;
end

print(example()); -- prints 5
1 Like