Working with Arrays

Indexes, length, searching, slicing, and the array of objects behind every list.

An object gives values names. An array gives them order, which is what a list of shows needs.

Making an array and reading it

Square brackets, values separated by commas. Positions start at 0.

const titles = ["Severance", "The Bear", "Shogun"];

console.log("first:   ", titles[0]);
console.log("last:    ", titles.at(-1));
console.log("count:   ", titles.length);
console.log("index 10:", titles[10]);

length is the number of items, so the last index is length - 1. Asking for titles[10] prints undefined. Reading past the end is not an error — the crash comes later, when something tries to use that undefined.

Counting from the end

Brackets cannot count backwards. titles[-1] looks for a property named "-1", and there is no such property.

const titles = ["Severance", "The Bear", "Shogun"];

console.log("titles[-1]:   ", titles[-1]);
console.log("titles.at(-1):", titles.at(-1));
console.log("titles.at(-2):", titles.at(-2));

at accepts a negative position and counts back from the end. Use it whenever you want the last item.

Adding and removing at the ends

push adds an item to the end. pop removes the last one and hands it back.

const titles = ["Severance", "The Bear"];

console.log("before:      ", titles);

// push returns the new length, not the array.
console.log("push returns:", titles.push("Shogun"));
console.log("after push:  ", titles);

console.log("pop returns: ", titles.pop());
console.log("after pop:   ", titles);

push returns: 3, because that is the new length. To see the array, log the array.

Both methods change titles in place. It is a const, and it still changed: const protects the name, not the value, as let, const, and Scope shows. React wants a new array instead of a changed one, and Destructuring, Spread, and Rest covers how to copy.

Searching

For plain values, includes answers yes or no, and indexOf gives a position.

const titles = ["Severance", "The Bear", "Shogun"];

console.log("includes The Bear:", titles.includes("The Bear"));
console.log("includes Andor:   ", titles.includes("Andor"));
console.log("indexOf Shogun:   ", titles.indexOf("Shogun"));
console.log("indexOf Andor:    ", titles.indexOf("Andor"));

indexOf gives -1 when there is no match. That is a number, and -1 is truthy, so never test a search with if (titles.indexOf(title)).

Finding a show

Both methods above compare with ===, so they cannot find an object by its fields. find takes a function instead, and gives you the first item that function says yes to.

const shows = [
  { id: 1, title: "Severance", year: 2022 },
  { id: 2, title: "The Bear", year: 2022 },
  { id: 3, title: "Shogun", year: 2024 },
];

const bear = shows.find((show) => show.title === "The Bear");
const andor = shows.find((show) => show.title === "Andor");

console.log("bear year: ", bear.year);
console.log("andor:     ", andor);
console.log("andor year:", andor?.year);

bear is the whole show object, so bear.year is 2022. Nothing matched "Andor", so andor is undefined. Reading andor.year would throw, which is why the last line uses ?. from Working with Objects.

Taking a section

slice copies a range: from the first index, up to but not including the second.

const titles = ["Severance", "The Bear", "Shogun", "Andor", "Dark"];

console.log("slice(0, 2):", titles.slice(0, 2));
console.log("slice(2):   ", titles.slice(2));
console.log("slice(-2):  ", titles.slice(-2));
console.log("original:   ", titles);

slice(0, 2) gives two titles, not three: index 2 is where it stops. With one argument it runs to the end, and a negative one counts back from the end. The original still has all five, unlike after a push or a pop.

An array of objects

This is the shape every list on screen is built from: an array, each item an object, each object carrying an id.

const shows = [
  { id: 1, title: "Severance", rating: 8.7 },
  { id: 2, title: "The Bear", rating: 8.6 },
  { id: 3, title: "Shogun", rating: 8.9 },
];

console.log("count: ", shows.length);
console.log("second:", shows[1].title);

// shows[5] is undefined, so reading .title from it throws.
try {
  console.log("shows[5].title:", shows[5].title);
} catch (error) {
  console.log("shows[5].title:", error.constructor.name);
}

console.log("shows[5]?.title:", shows[5]?.title);

Two steps happen on shows[1].title: read the item, then read its field. When the item is missing, the second step throws a TypeError, and ?. stops it.

The id is not decoration. When React renders this list, it uses the id as each row's key, so state stays attached to the right show.

Sorting compares text by default

sort turns each item into text before comparing, so 10 lands before 2.

const episodes = [10, 2, 1];

// slice() copies first: sort rearranges the array it is given.
console.log("sorted:  ", episodes.slice().sort());
console.log("original:", episodes);

The sorted line reads [1, 10, 2], because "10" comes before "2" as text. To sort numbers you pass sort a function that compares two of them, for example (a, b) => a - b.

Common mistakes

  • Using titles[-1]. Brackets do not count backwards; at(-1) does.
  • Searching objects with indexOf or includes. They compare with ===, so pass a function to find instead.
  • Reading a field straight off a find. It gives undefined when nothing matches, so use ?. or check first.

Exercise

Three lines, three wrong answers. Fix them so the output reads:

Next up: The Bear
Position: 1
Last: Shogun

The first prints the whole show instead of its title. The second searches a list of titles for a show object. The third uses a negative index the way brackets do not support.

const shows = [
  { id: 1, title: "Severance", watched: true },
  { id: 2, title: "The Bear", watched: false },
  { id: 3, title: "Shogun", watched: false },
];

const titles = ["Severance", "The Bear", "Shogun"];

console.log("Next up:", shows.find((show) => !show.watched));
console.log("Position:", titles.indexOf(shows[1]));
console.log("Last:", titles[-1]);