Table returns nil values when replicated to client from RemoteFunction callback on server

Hello! I am currently making a detain and search system. Handling the detain function of my system, I encountered this error where a table storing instances before they are destroyed in order to be used later return nil when replicated to the client.

Script used to handle callbacks and invocation of remotefunctions from the Detain and Search tools

detainFunction.OnServerInvoke = function(from, plr, detained)

	detain = detained

	--Fire to client passed args

	if detained == true then
		detainFunction:InvokeClient(plr, detained)
		
		--Empty table
		
		backpack = {}

		--Seize items

		for i,v in ipairs(plr.Backpack:GetChildren()) do
			table.insert(backpack, v) --Table successfully stores values 
			v:Destroy() --Destroy tools after they are stored
		end
	else
		detainFunction:InvokeClient(plr)

		--Return items
		--Make sure to communicate with search function is player has been seized from/being searched

	end
end

searchFunction.OnServerInvoke = function(from, args)

	--Set player

	if args:IsA("Player") then
		plr = args

		if detain == true then
        --Returns array Backpack from detain callback
			return backpack
		else
			--Visible to both the server and client, as player is not detained
			return args.Backpack
		end

	end

	--Handle seizure on server

	if args:IsA("Tool") then
		for i,v in ipairs(plr.Backpack:GetChildren()) do
			if v.Name == args.Name then
				v:Destroy()
			end
		end
	end
end

LocalScript handling Search Invocation

searchTool.Activated:Connect(function()
	for _,plr in ipairs(game:GetService("Players"):GetPlayers()) do
		if (player.Character.HumanoidRootPart.Position-plr.Character.HumanoidRootPart.Position).Magnitude <= 10 then
			if mouse.Target and mouse.Target.Parent == plr.Character then
				local backpack = searchFunction:InvokeServer(plr)
				
				--Handle UI
				
				for i,v in ipairs(searchUI.Frame.ScrollingFrame:GetChildren()) do
					if v:IsA("TextButton") then
						v:Destroy()
						print("Destroyed")
					end
				end
				
				if typeof(backpack) == "table" then
					--Table
					
					if backpack[1] == nil then
						print("No values")
					end
                    --Backpack is nil, despite server successfully storing backpack's values
					
					for i,v in ipairs(backpack) do
						print(v)
						local newItem = searchUI.Frame.Items.Item:Clone()
						newItem.Parent = searchUI.Frame.ScrollingFrame
						newItem.Text = v.Name
					end
				else
					--Instance
					for i,v in ipairs(backpack:GetChildren()) do
						local newItem = searchUI.Frame.Items.Item:Clone()
						newItem.Parent = searchUI.Frame.ScrollingFrame
						newItem.Text = v.Name
					end
				end

I tried this with a similar set up, where a table stored three BasePart instances before they were destroyed. It was able to successfully replicate to the client despite the instances being destroyed.

local detainFunction = game:GetService("ReplicatedStorage").RemoteFunctions.Detain

local searchFunction = game:GetService("ReplicatedStorage").RemoteFunctions.Search

local searchUI = game:GetService("ReplicatedStorage").UI.SearchUI

--Works
local backpack = {}

local detain 

local plr

detainFunction.OnServerInvoke = function(from, plr, detained)

	detain = detained

	--Fire to client passed args

	if detained == true then
		detainFunction:InvokeClient(plr, detained)

		--Empty table

		backpack = {}

		--Seize items

		for i,v in ipairs(workspace:GetChildren()) do
			if v:FindFirstChildWhichIsA("BoolValue") then
				table.insert(backpack, v)
				v:Destroy()
			end
		end

        --[[for i,v in ipairs(plr.Backpack:GetChildren()) do
            table.insert(backpack, v)
            v:Destroy()
        end]]--

	else
		detainFunction:InvokeClient(plr)

		--Return items
		--Make sure to communicate with search function is player has been seized from/being searched

	end
end

searchFunction.OnServerInvoke = function(from, args)

	--Set player

	if args:IsA("Player") then
		plr = args

		if detain == true then
			--Instances stored/created on the server can be passed from the callback and read by the client
			--Tables that store instances before they are destroyed on the server successfully replicate to the client
			return backpack
		else
			return args.Backpack
		end

	end

	--Handle seizure on server

	if args:IsA("Tool") then
		for i,v in ipairs(plr.Backpack:GetChildren()) do
			if v.Name == args.Name then
				v:Destroy()
			end
		end
	end
end

Output with Tool values in Backpack array:

Output with BasePart values in Backpack array:

Help and insight towards this post is very much appreciated, thanks in advance!

Destroying items after insertion means their references are also invalid. With remote functions lua is given enough yielding time to delete these references. There are potentially a lot more explanations as to why this doesn’t work, but the main take away should be to not send Instances over remote events/functions unless completely necessary. Isolate what parts of the instance you are using on the client and remote side, and just send those properties as base types.

Storing base data types like a string would be better as they will be duplicated into the table backpack and not deleted with the object. I would recommend checking the backpack items on the server like so

local backpack = {}

for i, v in ipairs(plr.Backpack:GetChildren()) do
    if v:IsA("Tool") then
        table.insert(backpack, v.Name)
        v:Destroy()
    end
end

and then you would give your search UI newItem.Text = v instead

1 Like

Thanks, this seems like a very sound explanation for my issue. Will check this out the soonest!