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.
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.
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.
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.
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.
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.
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.
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.
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.
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 signal getters, computed values, effects, or cleanup functions.
Save plain data only.