Skip to content

Rendering lists

.map() turns each entry of a list into a component. There are three kinds of list, told apart by their type: a constant array, a collection, and a container (why three).

A plain array literal is a constant array:

src/shop.ts2
const TABS = ['Buy', 'Sell', 'Info'];
src/shop.ts2
{TABS.map((label, i) => (
<layer x={i * 79} y={0} w={74} h={22} onClick={() => { setTab(i); }}>
{/* … the button … */}
</layer>
))}

The callback gets the entry and its index. Entries may be records, and each field keeps its type. An enum is a constant too.

A list or map you declared, or one of the game’s tables, is a collection:

src/lists.ts2
{PRAYERS.map((prayer, i) => (
<PrayerCell prayer={prayer} unlocked={prayer.level <= level}
x={(i % COLUMNS) * CELL} y={(i / COLUMNS) * CELL} />
))}

Arithmetic is whole-number, so i / COLUMNS is the row. A map’s callback takes the value, its key and its visible position:

src/wares.ts2
{CategoryNames.map((label, category, i) => (
<text x={i * 76} y={0} w={72} h={14} text={label} font={Fonts.SMALL}
valign="middle" shadow color={category === Category.TOOLS ? Colors.WHITE : Colors.DIM} />
))}

PRAYERS.length is the number of entries.

PrayerBook reads the player’s level, and each cell chooses between a lit sprite and a faded one:

src/lists.ts2
<layer x={x} y={y} w={CELL} h={CELL}>
{unlocked
? <sprite x="center" y="center" w={34} h={34} sprite={prayer.icon}
options={['Activate']} optionSubject={prayer.name} />
: <sprite x="center" y="center" w={34} h={34} sprite={prayer.icon} transparency={190} />}
</layer>

.filter() narrows a collection or a container before you map it:

src/lists.ts2
{PRAYERS
.filter((prayer) => prayer.level <= STARTING_LEVEL)
.map((prayer, i) => (
<sprite x={92 + i * 20} y="center" w={18} h={18} sprite={prayer.icon} />
))}

The .map() index counts only what survived, so the strip closes up. The filter’s own second parameter is the position before anything was dropped: (prayer, i) => i < 4 keeps the first four.

Use a conditional inside the row instead when the entry must keep its place, as an empty bank slot does.

.scan() is .map() carrying a sum, for rows of uneven height:

src/wares.ts2
{WARES.scan(
(ware, i, y) => <WareRow ware={ware} coins={coins} y={y} />,
(ware) => rowHeight(ware),
4,
)}

The second argument is what this row adds; the third is the start, default 0. The callback’s last parameter is the total as the row began, its top edge.

A container the server owns maps by slot number, empty slots included:

src/inventory.ts2
{inventory.map((item, slot) => (
<Slot item={item} count={inventory.count(slot)} slot={slot} />
))}

An empty slot holds -1. A container filters like a collection.

Position the rows of a collection or a container from the index or a running total:

.map((row, i) => <Row y={i * 24} … />)

Flow layout works for a constant array.