Skip to content

persistedState

persistedState declares a cell that survives the player closing the game. It is for preferences.

persistedState(initial: Int, key: string): StateRef<Int>
persistedState(initial: String, key: string): StateRef<String>

Call it at the top level of a file, never inside a component. Read it with useState.

src/settings.ts2
const roofRemoval = persistedState(1, 'docs.settings.roofs');
const shiftClickDrop = persistedState(0, 'docs.settings.shift-drop');
const dataOrbs = persistedState(1, 'docs.settings.orbs');
const levelUpMessages = persistedState(0, 'docs.settings.levelups');
  • initial: An Int or a String. The value on a device that has never saved one.
  • key: A string. The durable identity of this setting in the player’s saved file.

A declaration to pass to useState.

  • Renaming the variable is safe; changing the key declares a new setting and orphans the old value on every device that has one.
  • Two declarations cannot share a key (TS2E126).
  • Keys are tracked in a lockfile the project commits. Removing a declaration retires its key rather than freeing it, and the same key coming back recovers the values saved under it. See The state registry.
  • Saved per device, not per account. A player who plays on two machines has two copies.
  • This is for preferences, not for game data. Anything the game should know belongs to the server.
refused/persisted-state.ts2
const roofs = persistedState(1, "docs.refused.setting");
const orbs = persistedState(1, "docs.refused.setting"); // the same key twice
src/settings.ts2
const DisplaySettings = (): Component => {
const [roofs, setRoofs] = useState(roofRemoval);
src/settings.ts2
<layer x={0} y={0} w="fill" h={ROW_H} onClick={() => { setRoofs(1 - roofs); }}>
<CheckboxRow label="Remove roofs" on={roofs} />
</layer>

Name it for the setting, prefixed by the interface that owns it: 'docs.settings.roofs' says whose roofs.

src/settings.ts2
const restoreDefaults = (): void => {
const [, setRoofs] = useState(roofRemoval);
const [, setShiftDrop] = useState(shiftClickDrop);
const [, setOrbs] = useState(dataOrbs);
const [, setLevelUps] = useState(levelUpMessages);
const [, setLevel] = useState(zoom);
setRoofs(1);
setShiftDrop(0);
setOrbs(1);
setLevelUps(0);
setLevel(1);
};

Writing a persisted cell is how it is saved; there is no separate save.

The lockfile records every key that has ever existed, and CI refuses a build that would move one. If you meant to add a setting, commit the lockfile change along with the code. If you meant to rename a variable, rename the variable and leave the key alone.

Players lost their setting after a release

Section titled “Players lost their setting after a release”

Its key changed. The old values are still on disk under the old key — restoring the key restores them.

Ask what happens if it is lost. If the answer is “the player sets it again in a second”, session state is enough. Persist things a player would be annoyed to redo.