Skip to content

useState

useState is a hook that gives a component a cell — a value it can change — and makes the component follow it. Passed a number, the cell is private to the component; passed a declaration, the cell is the shared one the declaration names.

useState(initial: Int): [Int, Setter<Int>]
useState(declaration: StateRef<T>): [T, Setter<T>]

Passing an initial value gives this component its own cell. Every instance of the component has a separate copy, and no declaration is needed.

src/quantity.ts2
const PrivateToolbar = ({ x, y }: { x: Int; y: Int }): Component => {
const [amount, setAmount] = useState(0);
return (
<layer x={x} y={y} w={BUTTON_W * 4 + 6} h={BUTTON_H}>
{AMOUNTS.map((label, i) => (
<layer x={i * (BUTTON_W + 2)} y={0} w={BUTTON_W} h={BUTTON_H}
onClick={() => { setAmount(i); }}>
{amount === i
? <AmountButtonChosen label={label} />
: <AmountButton label={label} />}
</layer>
))}
</layer>
);
};
  • initial: An Int literal. The value the first time this instance appears, and ignored afterwards.

An array of exactly two values: the current value, and the setter that changes it.

  • A private cell holds a whole number. useState('') is refused (TS2E133). A string cell is a module-level sessionState or persistedState.
  • The initial value is a literal. An identifier in that position means the other form: useState(SECONDS) reads as “the cell declared as SECONDS” and is refused (TS2E124) when no such declaration exists. Write the number.
  • A boolean is written as 0 and 1, and flipped with 1 - value.
refused/use-state.ts2
const [name, setName] = useState(""); // ints only

Passing a sessionState or persistedState declaration reads the shared cell instead. Every component reading that declaration sees the same value.

src/quantity.ts2
const SharedToolbar = ({ x, y }: { x: Int; y: Int }): Component => {
const [amount, setAmount] = useState(sharedAmount);
  • declaration: A cell declared at the top level of a file.

The same pair — the current value, and a setter.

  • next: The new value, of the type the cell holds.

Nothing.

  • A setter can be called from anywhere — a handler, a function a handler calls, a component that never reads the value. Every reader follows, wherever it is, including in another interface that happens to be open.
  • The setter takes the value you want, not a function that computes it. Compute it first, from the value you already read.
  • Reading a cell in a handler does not make the component follow it; a read in the markup does. Read a value once at the top of a handler that derives several writes from it.
  • A cell that is written but never read is refused (TS2E228).
src/settings.ts2
const DisplaySettings = (): Component => {
const [roofs, setRoofs] = useState(roofRemoval);
const [shiftDrop, setShiftDrop] = useState(shiftClickDrop);
src/settings.ts2
<layer x={0} y={0} w="fill" h={ROW_H} onClick={() => { setRoofs(1 - roofs); }}>
<CheckboxRow label="Remove roofs" on={roofs} />
</layer>
src/shop.ts2
const openTab = sessionState(0);
src/shop.ts2
const [tab, setTab] = useState(openTab);
src/shop.ts2
const [tab] = useState(openTab);
src/settings.ts2
const restoreDefaults = (): void => {
const [, setRoofs] = useState(roofRemoval);
const [, setShiftDrop] = useState(shiftClickDrop);

A function that only writes leaves the value out of the pair.

src/menu.ts2
const lastChoice = sessionState('');
src/menu.ts2
const [chosen, setChosen] = useState(lastChoice);

A string cell is a module declaration, and it is tested by .length or used in a template; === between two strings is not supported.

src/once.ts2
const [stamina, setStamina] = useState(useServerStateOnce(Varbits.STAMINA_ACTIVE));

See useServerStateOnce.

Both copies of my component change together

Section titled “Both copies of my component change together”

You are reading a shared declaration where you wanted a private cell. useState(0) is per instance; useState(someDeclaration) is shared on purpose.

The screen does not update when I call the setter

Section titled “The screen does not update when I call the setter”

Check that the component displaying the value reads it in its markup. A component that receives the value as a prop from a parent that does not read it never hears about the change — move the read to whichever component should follow it.

Declare it at the top of the file with sessionState('') or persistedState('', key) and read that. A private cell cannot hold one.

Use several cells. There are no objects.