Skip to content

Client Functions

Client-side PlayerState API for data access and change listeners.

Client Only - READ-ONLY

Client scripts only. These functions are READ-ONLY - no data modification!
Need to change data? Create your own RemoteEvents with server validation.

Data Access Functions

Get()

Get(key) → any

Gets a top-level data value. Automatically waits for data to load with built-in caching for performance.

Parameters: key: string
Returns: any - The value, or nil if not found

Example
lua
local PlayerState = require(ReplicatedStorage.Libraries.PlayerState.PlayerStateClient)

local coins = PlayerState.Get("Coins")
local level = PlayerState.Get("Level")
print("Player has", coins, "coins and is level", level)

Note

Automatically waits for server data to sync with built-in caching for performance


GetPath()

GetPath(path) → any

Gets a nested data value using dot notation. Automatically waits for data to load with optimized nested value caching.

Parameters: path: ValuePath
Returns: any - The value, or nil if not found

Example
lua
local likes = PlayerState.GetPath("Plot.Likes")
local musicEnabled = PlayerState.GetPath("Settings.MusicEnabled")
local highScore = PlayerState.GetPath("Stats.HighScore")

print("Plot has", likes, "likes")
print("Music is", musicEnabled and "enabled" or "disabled")

Note

Handles nested data structures safely with optimized caching


GetAll()

GetAll() → PlayerData?

Gets all player data. Automatically waits for data to load.

Parameters: None
Returns: PlayerData? - Complete data table, or nil if not loaded

Example
lua
local allData = PlayerState.GetAll()
if allData then
    print("Complete player data:", allData)
    
    -- Iterate through top-level keys
    for key, value in pairs(allData) do
        print(key, "=", value)
    end
end

Note

Returns a reference to the actual data (do not modify!)

Read-Only Data

Don't modify the returned data! It's read-only on the client.


GetFromDict()

GetFromDict(dictPath, key) → any

Gets a value from a dictionary using a key with automatic type conversion and caching.

Parameters: dictPath: ValuePath, key: string | number
Returns: any - The value, or nil if not found

Example
lua
local PlayerState = require(ReplicatedStorage.Libraries.PlayerState.PlayerStateClient)

-- Get building data
local houseData = PlayerState.GetFromDict("Plot.Buildings", "House")
if houseData then
    print("House level:", houseData.Level)
    print("House material:", houseData.Material)
end

-- Get with numeric key (automatically converted to string)
local equippedItem = PlayerState.GetFromDict("Inventory.Equipped", 1)

-- Get setting value
local quality = PlayerState.GetFromDict("Settings", "GraphicsQuality")
print("Graphics quality:", quality or "Default")

Note

Automatically converts number keys to strings for consistency

Status Functions

IsReady()

IsReady() → boolean

Checks if player data is ready and replica is active.

Parameters: None
Returns: boolean - True if data is ready, false otherwise

Example
lua
if PlayerState.IsReady() then
    local coins = PlayerState.Get("Coins")
    print("Player has", coins, "coins")
else
    print("Data not ready yet...")
end

Note

Optional check - data access functions already wait automatically

Cache Management Functions

ClearCache()

ClearCache() → void

Manually clears all internal caches. Useful for memory management or debugging.

Parameters: None
Returns: Nothing

Example
lua
-- Clear cache when switching players or for debugging
PlayerState.ClearCache()

-- Cache will rebuild automatically on next access
local coins = PlayerState.Get("Coins")

Note

Manual cache management with automatic rebuild on next access

Change Listener Functions

OnChanged()

OnChanged(pathOrKey, callback) → ReplicaClient.Connection?

Listens for changes to a specific data path or key with enhanced change information.

Parameters: pathOrKey: string, callback: (newValue: any, oldValue: any, changeInfo: ChangeInfo | {string}?) -> ()
Returns: ReplicaClient.Connection? - Connection to disconnect the listener

lua
-- Listen to top-level changes
local coinsConnection = PlayerState.OnChanged("Coins", function(newValue, oldValue)
    print("Coins changed from", oldValue, "to", newValue)
    updateCoinsUI(newValue)
end)
lua
-- Listen to nested changes
local likesConnection = PlayerState.OnChanged("Plot.Likes", function(newValue, oldValue, pathInfo)
    print("Likes changed:", newValue)
    if pathInfo then
        print("Full path:", table.concat(pathInfo, "."))
    end
    updateLikesDisplay(newValue)
end)
lua
-- Listen to ALL changes with detailed action info
local allChangesConnection = PlayerState.OnChanged(".", function(newValue, oldValue, changeInfo)
    if changeInfo and changeInfo.action then
        local pathString = table.concat(changeInfo.path, ".")
        print(`{changeInfo.action}: {pathString} = {newValue} (was {oldValue})`)
        
        -- Handle different action types
        if changeInfo.action == "TableInsert" then
            print("Item added at index:", changeInfo.index)
        elseif changeInfo.action == "TableRemove" then
            print("Item removed from index:", changeInfo.index)
        end
    end
end)

Enhanced Change Information:

  • ChangeInfo type provides detailed action information
  • Support for "Set", "SetValues", "TableInsert", "TableRemove" actions
  • Index information for array operations
  • Full path information for all changes

Note

Callbacks fire immediately when data changes with automatic cache invalidation

Managing Connections
lua
-- Store connections for cleanup
local connections = {}

connections.coins = PlayerState.OnChanged("Coins", updateCoinsUI)
connections.level = PlayerState.OnChanged("Level", updateLevelUI)

-- NEW: Global listener with action handling
connections.all = PlayerState.OnChanged(".", function(newValue, oldValue, info)
    if info and info.action == "TableInsert" then
        print("Array item added!")
    end
end)

-- Cleanup when done
for name, connection in pairs(connections) do
    if connection then
        connection:Disconnect()
    end
end

Advanced Functions

GetReplica()

GetReplica() → ReplicaInstance?

Gets the raw Replica instance for advanced operations.

Parameters: None
Returns: ReplicaInstance? - Replica instance, or nil if not loaded

Example
lua
local replica = PlayerState.GetReplica()
if replica then
    -- Access raw Replica API
    print("Replica data:", replica.Data)
    print("Replica tags:", replica.Tags)
    
    -- Advanced change listener
    replica:OnChange(function(action, path, param1, param2)
        print("Raw change:", action, table.concat(path, "."))
    end)
end

Note

Provides access to the underlying Replica object with enhanced validation

Usage Examples

UI Updates with Enhanced Change Handling

Example
lua
local PlayerState = require(ReplicatedStorage.Libraries.PlayerState.PlayerStateClient)

-- UI Elements
local coinsLabel = gui.CoinsLabel
local levelLabel = gui.LevelLabel
local experienceBar = gui.ExperienceBar

-- Initial UI setup (data access automatically waits)
local function updateUI()
    coinsLabel.Text = tostring(PlayerState.Get("Coins") or 0)
    levelLabel.Text = "Level " .. tostring(PlayerState.Get("Level") or 1)
    
    local exp = PlayerState.Get("Experience") or 0
    local maxExp = (PlayerState.Get("Level") or 1) * 100
    experienceBar.Size = UDim2.new(exp / maxExp, 0, 1, 0)
end

-- Listen for changes with enhanced info
local connections = {}

connections.coins = PlayerState.OnChanged("Coins", function(newValue, oldValue, info)
    coinsLabel.Text = tostring(newValue or 0)
    
    -- Show gain/loss animation
    if oldValue and newValue > oldValue then
        showGainAnimation(newValue - oldValue)
    end
end)

connections.level = PlayerState.OnChanged("Level", function(newValue, oldValue, info)
    levelLabel.Text = "Level " .. tostring(newValue or 1)
    updateUI() -- Recalculate experience bar
    
    if oldValue and newValue > oldValue then
        showLevelUpEffect()
    end
end)

-- NEW: Global change listener for debugging
connections.debug = PlayerState.OnChanged(".", function(newValue, oldValue, info)
    if info and info.action then
        print(`[DEBUG] {info.action} at {table.concat(info.path, ".")}`)
    end
end)

-- Initial update
updateUI()

-- Enhanced cleanup function
local function cleanup()
    for name, connection in pairs(connections) do
        if connection and typeof(connection) == "table" and connection.Disconnect then
            connection:Disconnect()
        end
    end
    connections = {}
end

Settings Management

Example
lua
-- Settings UI manager with caching
local SettingsManager = {}

local PlayerState = require(ReplicatedStorage.Libraries.PlayerState.PlayerStateClient)
local SoundService = game:GetService("SoundService")

function SettingsManager.init()
    -- Apply initial settings (automatically waits for data)
    local musicEnabled = PlayerState.GetPath("Settings.MusicEnabled")
    local soundEnabled = PlayerState.GetPath("Settings.SoundEnabled")
    
    SoundService.AmbientReverb = musicEnabled and Enum.ReverbType.City or Enum.ReverbType.NoReverb
    SoundService.Volume = soundEnabled and 1 or 0
    
    -- Listen for changes
    PlayerState.OnChanged("Settings.MusicEnabled", function(enabled)
        SoundService.AmbientReverb = enabled and Enum.ReverbType.City or Enum.ReverbType.NoReverb
    end)
    
    PlayerState.OnChanged("Settings.SoundEnabled", function(enabled)
        SoundService.Volume = enabled and 1 or 0
    end)
end

return SettingsManager

Inventory Display with Array Change Handling

Example
lua
-- Inventory UI manager with enhanced change detection
local InventoryUI = {}

local PlayerState = require(ReplicatedStorage.Libraries.PlayerState.PlayerStateClient)

-- UI elements
local inventoryFrame = gui.InventoryFrame
local itemTemplate = inventoryFrame.ItemTemplate

function InventoryUI.updateDisplay()
    -- Clear existing items
    for _, child in pairs(inventoryFrame:GetChildren()) do
        if child ~= itemTemplate and child:IsA("Frame") then
            child:Destroy()
        end
    end
    
    -- Get inventory data
    local inventory = PlayerState.GetPath("Inventory") or {}
    
    -- Create UI elements for each item
    for index, item in ipairs(inventory) do
        local itemFrame = itemTemplate:Clone()
        itemFrame.Name = "Item" .. index
        itemFrame.Visible = true
        itemFrame.Parent = inventoryFrame
        
        -- Update item display
        itemFrame.ItemName.Text = item.Name or "Unknown"
        itemFrame.Rarity.Text = item.Rarity or "Common"
        itemFrame.LayoutOrder = index
    end
end

function InventoryUI.init()
    -- Initial display (automatically waits for data)
    InventoryUI.updateDisplay()
    
    -- NEW: Enhanced inventory change listener
    PlayerState.OnChanged("Inventory", function(newValue, oldValue, info)
        InventoryUI.updateDisplay()
        
        -- Handle specific actions
        if info and info.action == "TableInsert" then
            showItemAddedEffect(info.index)
        elseif info and info.action == "TableRemove" then
            showItemRemovedEffect(info.index)
        end
    end)
end

return InventoryUI

Error Handling

Safe Data Access with Readiness Checks

Example
lua
-- Safe data access (automatically waits for data)
local function getPlayerData()
    return {
        coins = PlayerState.Get("Coins") or 0,
        level = PlayerState.Get("Level") or 1,
        likes = PlayerState.GetPath("Plot.Likes") or 0
    }
end

-- Safe nested access with validation
local function getInventoryItem(index)
    local inventory = PlayerState.GetPath("Inventory")
    if not inventory or typeof(inventory) ~= "table" then
        return nil
    end
    
    return inventory[index]
end

-- Check if specific data exists
local function hasValidInventory()
    local inventory = PlayerState.GetPath("Inventory")
    return inventory and typeof(inventory) == "table" and #inventory > 0
end

Enhanced Connection Management

Example
lua
-- Advanced connection management system
local ConnectionManager = {}
local activeConnections = {}

function ConnectionManager.add(name, connection)
    -- Cleanup existing connection
    if activeConnections[name] then
        if typeof(activeConnections[name]) == "table" and activeConnections[name].Disconnect then
            activeConnections[name]:Disconnect()
        end
    end
    
    activeConnections[name] = connection
end

function ConnectionManager.remove(name)
    local connection = activeConnections[name]
    if connection and typeof(connection) == "table" and connection.Disconnect then
        connection:Disconnect()
    end
    activeConnections[name] = nil
end

function ConnectionManager.cleanup()
    for name, connection in pairs(activeConnections) do
        if connection and typeof(connection) == "table" and connection.Disconnect then
            connection:Disconnect()
        end
    end
    activeConnections = {}
end

-- Usage with enhanced error handling
ConnectionManager.add("coins", PlayerState.OnChanged("Coins", function(newValue, oldValue, info)
    if typeof(newValue) == "number" then
        updateCoinsDisplay(newValue)
    end
    else
        warn("Invalid coins value received:", newValue)
    end
end))

-- Cleanup when done
ConnectionManager.cleanup()

Performance Tips

Caching & Optimization

Automatic Caching: Frequently accessed values are cached automatically for performance Cache Management: Use ClearCache() only when necessary (debugging, memory concerns) Readiness Checks: Use IsReady() before bulk operations to avoid unnecessary waits Specific Listeners: Use specific paths instead of global "." listeners when possible Connection Cleanup: Always disconnect listeners to prevent memory leaks

Best Practices

Fallbacks: Always provide fallbacks when accessing data that might not exist Auto-Waiting: Data access automatically waits - no manual waiting required Conditional Logic: Use IsReady() only for conditional logic or optimization Listener Management: Disconnect listeners when UI elements are destroyed Path Specificity: Use specific paths instead of listening to all changes Action Handling: Handle different action types in global listeners for better UX Read-Only Data: Don't modify returned data - it's a reference to actual data

PlayerState - High-Performance Roblox Data Management