Lists
This week we slow down just a bit and focus on lists. We will first learn how to use the array methods in JavaScript to manipulate lists, and then we will learn how to use React to render lists of elements. Finally, we will learn how to use Tailwind CSS to make our pages responsive and look good on all screen sizes.
JS Skills
Arrays
In JavaScript, arrays are high-level, list-like objects that are incredibly versatile and serve as one of the basic data structures in the language. Understanding arrays and their common operations is crucial for any JavaScript developer.
Array Notation
Arrays in JavaScript are created and accessed using square bracket notation. Here is how you declare an array:
let myArray = ["apple", "banana", "cherry"];
In this example, myArray is an array containing three strings. The elements in the array are ordered and each one has an index, starting from 0. So, 'apple' is at index 0, 'banana' is at index 1, and 'cherry' is at index 2. You can access the elements of the array using their indexes like so:
console.log(myArray[0]); // Output: 'apple'
An empty array is an array that does not contain any elements. You can declare an empty array like this:
let emptyArray = [];
Even though the array is initially empty, you can still add elements to it later on which will increase by internally increasing the length of the array.
Array Operations
Arrays come with a wide range of built-in methods that allow you to manipulate and interact with them. These methods can be categorized based on whether they mutate (change) the original array or not, and what kind of output they return. The following is a list of the most common array methods, grouped by their category. There are many more methods available, but these are the ones you will use most often.
Methods that mutate the original array
push
: Add elements to the end of an array.pop
: Remove the last element from an array.splice
: Remove (or add) elements from a specific array index.
// Start with an array of numbers
let numbers = [1, 2, 3, 4, 5];
console.log(numbers); // Output: [1, 2, 3, 4, 5]
// push: Add elements to the end of an array.
numbers.push(6);
console.log(numbers); // Output: [1, 2, 3, 4, 5, 6]
// pop: Remove the last element from an array.
let lastElement = numbers.pop();
console.log(lastElement); // Output: 6
console.log(numbers); // Output: [1, 2, 3, 4, 5]
// splice: Remove elements from a specific array index.
// Example of removing elements: Remove 2 elements starting from index 1
numbers.splice(1, 2);
console.log(numbers); // Output: [1, 4, 5]
In the above examples, we started with an array of numbers from 1 to 5. We then used push to add 6 to the end of the array. After that, we used pop to remove the last element (6) from the array. Lastly, we demonstrated splice to remove elements from the array (removing 2 and 3).
Other valuable methods to consider include: sort
, shift
(remove first element of the array), unshift
(add elements to beginning of the array), and reverse
.
Methods that return a new array (do not mutate the original array):
map
: Create a new array with the result of a function on each element.filter
: Create a new array with elements that pass a test.concat
: Merge two or more arrays.slice
: Return a shallow copy of a portion of an array.toSorted
: Create a new array with the elements sorted.
// Start with an array of numbers
let numbers = [5, 3, 9, 1, 4];
console.log(numbers); // Output: [5, 3, 9, 1, 4]
// map: Create a new array with the result of a function on each element.
// the arrow function takes each element of the array and multiplies it by 2
let doubled = numbers.map((num) => num * 2);
console.log(doubled); // Output: [10, 6, 18, 2, 8]
// filter: Create a new array with elements that pass a test.
// the arrow function takes each element of the array and returns true if it is less than 5
let lessThanFive = numbers.filter((num) => num < 5);
console.log(lessThanFive); // Output: [3, 1, 4]
// concat: Merge two or more arrays.
let moreNumbers = [6, 7, 8];
let allNumbers = numbers.concat(moreNumbers);
console.log(allNumbers); // Output: [5, 3, 9, 1, 4, 6, 7, 8]
// slice: Return a shallow copy of a portion of an array.
// slice takes two parameters: start index (inclusive) and end index (not inclusive)
let someNumbers = numbers.slice(1, 3);
console.log(someNumbers); // Output: [3, 9]
// toSorted: Create a new array with the elements sorted.
// the arrow function compares two numbers and returns the difference between them
// if the difference is negative, a goes before b
// if the difference is positive, b goes before a
let sortedNumbers = numbers.toSorted((a, b) => a - b);
console.log(sortedNumbers); // Output: [1, 3, 4, 5, 9]
My sincere apologies! toSorted()
is too new. It is available in the browser
but not with Node.js. In the meantime in your React code, please use the
mutating sort()
method instead.
Other valuable methods to consider include: fill
(fill all elements of an array with a static value) and flat
(flatten nested arrays into a single array).
Methods that return a specific output (do not mutate the original array):
reduce
: Apply a function against an accumulator and each element (reduces the array to a single output value).join
: Join all elements of an array into a string.
// Start with an array of numbers
let numbers = [1, 2, 3, 4, 5];
console.log(numbers); // Output: [1, 2, 3, 4, 5]
// reduce: Apply a function against an accumulator and each element (reduces the array to a single output value).
// the arrow function returns the sum of the accumulator and the current value
// the second parameter of reduce is the initial value of the accumulator
let sum = numbers.reduce(
(accumulator, currentValue) => accumulator + currentValue,
0
);
console.log(sum); // Output: 15
// join: Join all elements of an array into a string.
let joined = numbers.join(", ");
console.log(joined); // Output: '1, 2, 3, 4, 5'
reduce
is a very powerful method that can be used to solve a wide range of problems.
Other valuable methods to consider include: forEach
(loop through each element of the array and perform an action), find
(return the first element that passes a test), findIndex
(return the index of the first element that passes a test), some
(return true if at least one element passes a test), every
(return true if all elements pass a test), includes
(return true if an array contains a specific element), and indexOf
(return the index of a specific element).
JavaScript Syntax Used with Arrays
The Spread Operator (...
)
The Spread Operator (...
) in JavaScript is used to spread the elements of an iterable (like an array, string, or object literals) into individual elements. It allows an iterable to expand in places where zero or more arguments or elements are expected.
For example, it can be used to combine arrays:
let array1 = [1, 2, 3];
let array2 = [4, 5, 6];
let combined = [...array1, ...array2];
console.log(combined); // Output: [1, 2, 3, 4, 5, 6]
Array Destructuring
Array Destructuring is a syntax feature that allows you to unpack values from arrays, or properties from objects, into distinct variables.
For example, you can unpack the first two elements of an array like this:
let array = ["apple", "banana", "cherry"];
let [firstFruit, secondFruit] = array;
console.log(firstFruit, secondFruit); // Output: 'apple' 'banana'
In this example, Array Destructuring is used to assign the first two elements of array to the variables firstFruit and secondFruit, respectively.
You've already seen array destructuring in action from last week, right? When we were using the useState hook, we were using array destructuring to unpack the first and second elements of the array returned by useState into the state variable and the set state function, respectively. For example:
const [count, setCount] = useState(0);
Other valuable methods to consider include: for...of
Loop (loop through each element of the array), in
Operator (check if an object has a specific property
Arrays and Objects
Array of Objects
Arrays can contain objects as elements. For example:
let people = [
{ name: "John", age: 30 },
{ name: "Jane", age: 25 },
{ name: "Jack", age: 40 },
];
In this example, people is an array of objects. Each object has two properties: name and age. You can access the elements of the array using their indexes like so:
console.log(people[0]); // Output: { name: 'John', age: 30 }
console.log(people[1].name); // Output: 'Jane'
Further Nesting of Arrays and Objects
Arrays and objects can be nested inside each other. For example:
let people = [
{
name: "John",
age: 30,
addresses: [
{ type: "home", street: "123 Main St", city: "Calgary" },
{ type: "vacation", street: "123 Lakeshore Ave", city: "Pigeon Lake" },
],
},
{
name: "Jane",
age: 25,
addresses: [],
},
];
console.log(people[0].addresses[1].city); // Output: 'Pigeon Lake'
In this example, people is an array of objects. Each object has three properties: name, age, and addresses. The third property, addresses, is an array of objects. Each object in the addresses array has three properties: type, street, and city.
Tailwind CSS Skills
Responsive Design
Responsive design is an approach to web page creation that makes use of flexible layouts, flexible images and cascading style sheet media queries. The goal of responsive design is to build web pages that detect the visitor's screen size and orientation and change the layout accordingly.
Tailwind CSS has built-in support for responsive design. You can use the following classes to make your pages responsive:
sm:
Apply the following styles on screens that are 640px wide or wider.md:
Apply the following styles on screens that are 768px wide or wider.lg:
Apply the following styles on screens that are 1024px wide or wider.xl:
Apply the following styles on screens that are 1280px wide or wider.2xl:
Apply the following styles on screens that are 1536px wide or wider.
Here's an example of a responsive page that uses Tailwind CSS. Try resizing the browser window to see how the layout changes based on screen size. (Mind you, it's a little tricky because this whole documentation site is also responsive, so you'll have to resize the browser window and scroll to this demo to see the effect.)
<header className="text-center py-5 bg-blue-500 text-white">
<h1 className="text-4xl">My Responsive Webpage</h1>
</header>
<nav className="bg-blue-100 p-5 block sm:flex justify-between">
<Link href="#" className="block sm:inline-block m-2 p-2 bg-blue-500 text-white rounded">Home</Link>
<Link href="#" className="block sm:inline-block m-2 p-2 bg-blue-500 text-white rounded">About</Link>
<Link href="#" className="block sm:inline-block m-2 p-2 bg-blue-500 text-white rounded">Contact</Link>
</nav>
<main className="p-5 grid sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="p-5 bg-blue-500 rounded">Content 1</div>
<div className="p-5 bg-blue-600 rounded">Content 2</div>
<div className="p-5 bg-blue-700 rounded">Content 3</div>
<div className="p-5 bg-blue-800 text-white rounded">Content 4</div>
</main>
My Responsive Webpage
Here's what the classes in this example do:
- The
sm:
classes apply to screens that are 640px wide or more. - The
lg:
classes apply to screens that are 1024px wide or more. - The
block
,sm:inline-block
, andsm:flex
classes change the layout of the navigation links and main content based on screen size. - The
grid
,sm:grid-cols-2
, andlg:grid-cols-4
classes create a responsive grid layout for the main content.
React Skills
Rendering Lists
Rendering lists is an essential skill when working with React. It involves creating a set of React elements for each item in a list and displaying them in the user interface.
React uses JavaScript's map method to iterate through an array and create a new array of React elements. Each list item should have a unique key prop, which helps React optimize the rendering by quickly identifying which items have changed, are added, or are removed. If you don't specify a key prop, React will display a warning in the console.
Here's an example of rendering a list of items in React:
const items = ["apple", "banana", "cherry"];
function FruitList() {
return (
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
);
}
The map function is being used to create a new array of JSX elements (specifically, <li>
elements) from the items array.
Here is a step-by-step explanation:
items.map((item) => (...))
is calling the map function on the items array. map is a built-in JavaScript function that creates a new array with the results of calling a provided function on every element in the calling array.(item) => (...)
is an arrow function that is provided as an argument to the map function. This function will be called for each element in the items array, with the current element being passed as an argument (item
).<li key={item}>{item}</li>
is the JSX that is returned for each element in the items array. The key prop is set to the value of the current item, and the text content of the<li>
element is also set to the value of the current item. This creates a new<li>
element for each item in the items array.- Finally,
{items.map((item) => (<li key={item}>{item}</li>))}
is where the new array of<li>
elements is inserted into the JSX. When React renders this component, it will replace this expression with the actual array of elements.
The result is that FruitList will render a list of fruits, with each fruit in the items array being displayed as a separate list item:
- apple
- banana
- cherry
Handling Events in Lists
Handling events in lists is about understanding how to manage user interactions, such as clicks or form submissions, within a list of items in a React application. When an event occurs on a list item, you often need to know which specific item it occurred on. This is typically achieved by passing additional parameters to your event handlers or by creating unique event handlers for each item.
Here's an example of handling events in a list in React:
const items = ["apple", "banana", "cherry"];
function FruitList() {
function handleClick(item) {
console.log(`You clicked on ${item}`);
}
return (
<ul>
{items.map((item) => (
<li key={item} onClick={() => handleClick(item)}>
{item}
</li>
))}
</ul>
);
}
In this example, the onClick event handler is used in combination with the map function to add a click event to each list item in the FruitList component. The event handling part is centered around the handleClick
function and the onClick
prop.
Here's how it works:
function handleClick(item) {...}
: This function is an event handler, which logs a message to the console indicating which fruit was clicked. It takes one argument, item, which represents the name of the fruit.onClick={() => handleClick(item)}
: This is where the handleClick function is hooked up to the click event of the list item (<li>
). TheonClick
prop is a special prop in React that can be used to register click events.() => handleClick(item)
: This is an arrow function that calls handleClick(item). It's necessary to wraphandleClick(item)
in another function because you want to pass item as an argument to handleClick. If you wroteonClick={handleClick(item)}
, handleClick would be called immediately when the component renders, not when the list item is clicked.
When you click on a list item in this rendered list, the handleClick function will be called with the item that was clicked on, and a message will be logged to the console.
Example - Selecting a Fruit
Here's an example of a React component that renders a list of fruits and allows the user to select a fruit from the list. The selected fruit is displayed in the user interface.
🗒️ Summary
- 🎯 Array Notation: Arrays can be declared using square bracket notation, and their elements accessed via indices.
- 🛠️ Array Methods: JavaScript arrays come with built-in methods for manipulation:
- 🔄 Mutating Methods: Methods that mutate the original array include push, pop, and splice.
- 🔀 Non-mutating Methods: Methods that return a new array include map, filter, concat, and slice.
- 1️⃣ Specific Output Methods: Methods that return a specific output and don't mutate the original array include reduce and join.
- 🧰 JavaScript Syntax with Arrays: Other JavaScript syntax used with arrays include the Spread Operator (...) for expanding elements and Array Destructuring for unpacking values from arrays.
- 🏗️ Arrays and Objects: Arrays can contain objects and can be further nested, creating complex data structures for versatile use.
- 🖥️ Responsive Design: Responsive design in Tailwind CSS is achieved using specific classes for different screen sizes (sm:, md:, lg:, xl:, 2xl:).
- 🔑 Rendering Lists in React: React makes use of JavaScript's map method to render lists of elements, with each list item requiring a unique key prop.
- 🎛️ Handling Events in Lists: Event handling in lists involves managing user interactions with specific list items. Events are handled by passing additional
📚 Knowledge Check
filter
concat
reduce
map
Screens that are 640px wide or wider
Screens that are less than 640px wide
Small screen devices like phones and tablets
All screen sizes
To ensure that each item has a unique identifier for styling
To help React optimize the rendering by quickly identifying which items have changed, are added, or are removed
To prevent warnings in the console
To improve the performance of the map function
It registers a click event on a list item
It defines a function to be executed when a list item is clicked
It changes the style of a list item when it's clicked
It enables a list item to be clicked
[1, 2, 3, 4, 5].map((num) => num ** 2)
[1, 2, 3, 4, 5].filter((num) => num ** 2)
[1, 2, 3, 4, 5].reduce((num) => num ** 2)
All of the above
[1, 2, 3, 4, 5].map((acc, num) => acc + num, 0)
[1, 2, 3, 4, 5].reduce((acc, num) => acc + num, 0)
[1, 2, 3, 4, 5].filter((acc, num) => acc + num, 0)
None of the above
['apple', 'banana', 'cherry'].join(', ')
['apple', 'banana', 'cherry'].reduce((acc, item) => acc + ', ' + item, '')
['apple', 'banana', 'cherry'].map((item) => item + ', ').join('')
['apple', 'banana', 'cherry'].concat(', ')
[10, 12, 3, 4, 2].reduce((max, num) => num > max ? num : max)
Math.max(...[10, 12, 3, 4, 2])
[10, 12, 3, 4, 2].toSorted((a, b) => b - a)[0]
All of the above
Use a for loop to create a new array of JSX elements.
Use the map() method to create a new array of JSX elements.
Use the forEach() method to create a new array of JSX elements.
Use an if statement to create a new array of JSX elements.
The text 'Clicked!' will be printed to the console.
A new function will be created that logs 'Clicked!' to the console, but it won't be called.
An error will occur because console.log('Clicked!') is not a valid event handler.
The text 'Click me' will change to 'Clicked!'.
An unordered list with the items 'apple', 'banana', and 'cherry'.
An error message because 'key' is not a valid prop.
An unordered list with one item: 'apple, banana, cherry'.
Nothing, because the map function is not used correctly.
🏃 Activity
In this activity, your task is to implement a filter for the displayedItems
array in the FruitList
component. The filter should be applied based on the filter
state. When the filter
state is true
, displayedItems
should only include fruits that start with the letter 'a'. When the filter
state is false
, displayedItems
should include all fruits.
💡 Hints
Hint 1
Replace the code on line 15. You'll need to use a JavaScript array method that allows you to create a new array with only the elements that pass a certain condition. This method is called on the original array and takes a function as an argument. This function is called for each item in the array and should return true
for items you want to include in the new array.
Hint 2
The JavaScript array method you need is filter()
. This method is called on the items
array. The function you pass to filter()
should return true
for items that start with the letter 'a' and false
otherwise. Remember to use this method only when the filter
state is true
.
Hint 3
You can use a ternary operator to choose between the filtered and unfiltered list based on the filter
state. The filter()
method call should look like this: items.filter(item => item.startsWith('a'))
. Remember, the startsWith()
method is case-sensitive. If your list contains items that start with an uppercase 'A', you might want to convert them to lowercase using the toLowerCase()
method before checking if they start with 'a'.
✅ Solution
🌐 Community Events App
Week 5 of the Community Events app demonstrates how to display a list of event objects. Focus on how the EventsList
component uses the map()
method to render a list of EventCard
components.