Skip to main content

DataStore Flow

This example shows a simple DataStore flow with Reheaven.

Data is still loaded, edited, and saved as normal Lua tables.

Reheaven is only used to mirror selected data into reactive state.

Key Point

Use plain tables for saving data. Use Reheaven for UI, replication, and reactive runtime behavior.

Plain Data

Your saved data can stay as a normal table.

DefaultData.lua
local function createDefaultData()
return {
coins = 0;
level = 1;
xp = 0;
}
end

Reactive Mirror

Create signals for the values you want to display or react to.

PlayerDataMirror.lua
local coins, setCoins = Reheaven.signal(0)
local level, setLevel = Reheaven.signal(1)
local xp, setXP = Reheaven.signal(0)
local loaded, setLoaded = Reheaven.signal(false)

local progress = Reheaven.computed(function()
return xp() / 100
end)

Loading Data

Load the plain table first.

Then copy the loaded values into the reactive mirror.

LoadData.lua
local playerData = {}

local function loadPlayerData(player)
local savedData = DataStore:GetAsync(player.UserId)

playerData[player] = savedData or createDefaultData()

local data = playerData[player]

Reheaven.batch(function()
setCoins(data.coins)
setLevel(data.level)
setXP(data.xp)
setLoaded(true)
end)
end

Editing Data

Change the plain table directly.

Then update the reactive mirror.

PlayerDataActions.lua
local function addCoins(player, amount)
local data = playerData[player]

if not data then
return
end

data.coins += amount

setCoins(data.coins)
end

local function addXP(player, amount)
local data = playerData[player]

if not data then
return
end

data.xp += amount

if data.xp >= 100 then
data.xp = 0
data.level += 1
end

Reheaven.batch(function()
setXP(data.xp)
setLevel(data.level)
end)
end

Saving Data

Use UpdateAsync to save the plain table.

SaveData.lua
local function savePlayerData(player)
local data = playerData[player]

if not data then
return
end

DataStore:UpdateAsync(player.UserId, function()
return {
coins = data.coins;
level = data.level;
xp = data.xp;
}
end)
end

UI From Mirror State

The UI reads the reactive mirror, not the DataStore table directly.

PlayerDataUI.lua
local effect = Reheaven.effect

effect(function()
coinsLabel.Text = `Coins: {coins()}`
levelLabel.Text = `Level: {level()}`
xpLabel.Text = `XP: {xp()}`
end)

effect(function()
xpBar.Size = UDim2.fromScale(progress(), 1)
end)

effect(function()
loadingFrame.Visible = not loaded()
end)

Auto Save

Autosave still saves the plain table.

AutoSave.lua
task.spawn(function()
while true do
task.wait(60)

for _, player in Players:GetPlayers() do
savePlayerData(player)
end
end
end)

Player Leaving

Save the plain data, then clean it up.

PlayerRemoving.lua
Players.PlayerRemoving:Connect(function(player)
savePlayerData(player)

playerData[player] = nil
end)

Data Flow

DataStore loads plain table

Plain table is edited by game logic

Reactive mirror updates

UI or replication reacts

Plain table is saved with UpdateAsync
Do Not Save Reactive Objects

Do not save signal getters, computed values, effects, or cleanup functions.

Save plain data only.