Skip to content

a

Rendering a collection, modules

Before starting a new part, let's add some additional tools to our IDE and quickly remind ourselves about arrays and event handlers. For now, you can continue by opening up the previous lab that we used in part 1.

New Tool: WebStorm Live Templates

With WebStorm it's easy to create and live templates, i.e., shortcuts/snippets for quickly generating commonly re-used portions of code, much like how sout works in NetBeans and psvm works in IntelliJ.

Try erasing everything in App.jsx, then type rsc and then hit the Tab key and see the magic that appears!

Instructions for working with live templates can be found here. The main idea is that in certain contexts, you would be able to type a shortcut, and WebStorm will provide you with some code that you would normally write.

There are already lots of live templates that are available for you to review in Settings->Editor->Live Templates. Specifically, you can find the rsc template in the React section. You should also peruse the React hooks section to see things like useState.

One popular live template/shortcut is log, which replaces having to type out console.log().

To use it, type log and hit Tab to autocomplete.

Though it doesn't stop there, as you can make custom live templates.

For example, I'm going to take this tutorial and reduce it to its essence and apply it to our new template.

Let's set up a custom clog live template.

  1. Go to Settings->Editor->Live Templates
  2. With Javascript Selected, click on the + icon, and select 1 Live Template
  3. Type clog for the Abbreviation and More detailed console.log for the Description
  4. Paste this in the template text:

    console.log('$PARAM_TEXT$(' + typeof $PARAM$ + ') =', $PARAM$, ' | $FILE$:$LINE$ - $EXPECTED$')$END$
  5. Select Reformat according to style
  6. Click the Define link below the template text and select Javascript & Typescript.
  7. Finally, click Edit variables... and make it look like this, make sure you have the order of the variables as well, where PARAM comes first.

    1. Use the up and down buttons highlighted to move the sequence of the variables.
image showing the variables and values

Click OK twice and then try it out by typing clog inside your App Javascript function and then Enter. Then type the name of the variable props (or whatever other variable you have) and Tab again. You should notice that it will display the word you typed in two places on that line. First, as the name in the string for the log statement and then as the variable to print. Finally, if you have some expected value of what you think it should be at that point, you can type that in as well, and then Enter one last time. You'll notice that you have this template that will provide you with a varialbe, its type, the filename and line number to make it easier to debug!

JavaScript Arrays

From here on out, we will be using JavaScript's functional programming conventions (e.g. find, filter, and map) for Arrays, all of the time. They operate on the same general principles as their equivalents in other languages and as streams in Java.

If operating arrays with functional operators makes you feel uneasy, it is worth watching at least the first three parts of the YouTube video series Functional Programming in JavaScript:

Event Handlers Revisited

Event handling is complex.

It's worth reviewing the previous section on event handlers.

Passing event handlers to the child components of the App component can also be complex, so you may want to revisit the material that was provided about the topic here.

Rendering Collections

We will now do the 'frontend', or the browser-side application logic, in React for an application that's similar to the example application from part 0

Let's start with the following in App.jsx:

const App = (props) => {
  const { tasks } = props

  return (
    <div>
      <h1>Tasks</h1>
      <ul>
        <li>{tasks[0].content}</li>
        <li>{tasks[1].content}</li>
        <li>{tasks[2].content}</li>
      </ul>
    </div>
  )
}

export default App

We'll also need this in main.jsx:

import ReactDOM from 'react-dom/client'

import App from './App'

const tasks = [
  {
    id: 1,
    content: 'Wash the dishes',
    date: '2023-01-10T17:30:31.098Z',
    important: true
  },
  {
    id: 2,
    content: 'Take out the trash',
    date: '2023-01-10T18:39:34.091Z',
    important: false
  },
  {
    id: 3,
    content: 'Buy salty snacks',
    date: '2023-01-10T19:20:14.298Z',
    important: true
  }
]

ReactDOM.createRoot(document.getElementById('root')).render(
  <App tasks={tasks} />
)

Every task contains:

  • a unique id.
  • content describing the task
  • a date,
  • a boolean for marking whether the task is important

The example above works only because there are exactly three tasks in the array.

A single task is rendered by accessing the objects in the array by referring to a hard-coded index number:

<li>{tasks[1].content}</li>

This is, of course, not elegant. We can improve on this by generating React elements from the array objects using the map function.

tasks.map(task => <li>{task.content}</li>)

The result is an array of li elements.

[
  <li>Wash the dishes</li>,
  <li>Take out the trash</li>,
  <li>Buy salty snacks</li>,
]

Which can then be placed inside ul tags:

const App = (props) => {
  const { tasks } = props

  return (
    <div>
      <h1>Tasks</h1>
      <ul>        {tasks.map(task => <li>{task.content}</li>)}      </ul>    </div>
  )
}

Because the code generating the li tags is JavaScript, it must be wrapped in curly braces in a JSX template just like all other JavaScript code.

We will also make the code more readable by separating the arrow function's declaration across multiple lines:

const App = (props) => {
  const { tasks } = props

  return (
    <div>
      <h1>Tasks</h1>
      <ul>
        {tasks.map(task => 
          <li>            {task.content}          </li>        )}
      </ul>
    </div>
  )
}

Key-attribute

Even though the application seems to be working, there is a warning in both WebStorm:

WebStorm key prop error

and the console:

unique key prop console error

As the linked React page in the error message suggests; the list items, i.e. the elements generated by the map method, must each have a unique key value: an attribute called key.

Let's add the keys:

const App = (props) => {
  const { tasks } = props

  return (
    <div>
      <h1>Tasks</h1>
      <ul>
        {tasks.map(task => 
          <li key={task.id}>            {task.content}
          </li>
        )}
      </ul>
    </div>
  )
}

And the error message disappears.

React uses the key attributes of objects in an array to determine how to update the view generated by a component when the component is re-rendered. More about this is in the React documentation.

Map

Understanding how the array method map works is crucial for the rest of the course.

Let's revisit the tasks array that is currently in main.jsx:

const tasks = [
  {
    id: 1,
    content: 'Wash the dishes',
    date: '2023-01-10T17:30:31.098Z',
    important: true
  },
  {
    id: 2,
    content: 'Take out the trash',
    date: '2023-01-10T18:39:34.091Z',
    important: false
  },
  {
    id: 3,
    content: 'Buy salty snacks',
    date: '2023-01-10T19:20:14.298Z',
    important: true
  }
]

If the following code is added at the end of main.jsx:

const result = tasks.map(task => task.id)
console.log(result)

An array [1, 2, 3] will be viewable in the console. map always creates a new array, the elements of which have been created from the elements of the original array by mapping: using the function given as a parameter to the map method.

The function in our example is:

task => task.id

This is an arrow function written in compact form. The full form would be:

(task) => {
  return task.id
}

The function gets a task object as a parameter and returns the value of its id field.

Changing our map line to have the parameter:

const result = tasks.map(task => task.content)

results in the console containing the array ['Wash the dishes', 'Take out the trash', 'Buy salty snacks'].

This is already pretty close to the React code we used:

tasks.map(task =>
  <li key={task.id}>
    {task.content}
  </li>
)

This code generated a li tag containing the contents of the task from each task object.

Because the function parameter passed to this map method -

task => <li key={task.id}>{task.content}</li>

is used to create view elements, the value of the variable must be rendered inside curly braces. What happens if one or more of the braces are removed? Play around with removing and re-adding braces for the task in App.jsx.

The use of curly braces will cause some pain in the beginning, but you will get used to them soon enough. The visual feedback from React is immediate.

Anti-pattern: Array Indexes as Keys

We could have made the error message on our console disappear by using the array indexes as keys. The indexes can be retrieved by passing a second parameter to the callback function of the map method:

tasks.map((task, i) => ...)

When called like this, i is assigned the value of the index of the position in the array where the task resides.

As such, one way to define the row generation without getting errors is:

<ul>
  {tasks.map((task, i) => 
    <li key={i}>
      {task.content}
    </li>
  )}
</ul>

This is, however, 🐞 not recommended 🐞 and can create undesired problems even if it seems to be working just fine.

Read more about this in this article.

Refactoring Modules

Let's tidy the code up a bit.

We are only interested in the field tasks from props, so let's retrieve that directly using destructuring:

const App = ({ tasks }) => {  return (
    <div>
      <h1>Tasks</h1>
      <ul>
        {tasks.map(task => 
          <li key={task.id}>
            {task.content}
          </li>
        )}
      </ul>
    </div>
  )
}

If you have forgotten what destructuring means and how it works, please review the section on destructuring.

We'll separate displaying a single task into its own component Task:

const Task = ({ task }) => {  return (    <li>{task.content}</li>  )}
const App = ({ tasks }) => {
  return (
    <div>
      <h1>Tasks</h1>
      <ul>
        {tasks.map(task =>           <Task key={task.id} task={task} />        )}      </ul>
    </div>
  )
}

Notice that the key attribute must now be defined in the Task component, and not in the li tags like before.

A whole React application can be written in a single file. Although that is, of course, not very practical. Common practice is to declare each component in its own file as an ES6 module.

We have been using modules the whole time. These lines from main.jsx:

import ReactDOM from 'react-dom/client'

import App from './App'

import two modules, enabling them to be used in that file. The module react-dom/client is placed into the variable ReactDOM, and the module that defines the main component of the app is placed into the variable App

Let's move our Task component into its own module.

In smaller applications, components are usually placed in a directory called components, which is in turn placed within the src directory. The convention is to name the file after the component.

Create a directory in src called components for our reading application and place a file named Task.jsx inside. If WebStorm asks about adding the file, make sure to check not to ask again if you haven't done so already. So reading/src/components/Task.jsx should have the following:

const Task = ({ task }) => {
  return (
    <li>{task.content}</li>
  )
}

export default Task

The last line of the module exports the declared module, the variable Task.

Now the file that is using the component - App.jsx - can import the module:

import Task from './components/Task'
const App = ({ tasks }) => {
  // ...
}

You can try typing import in the file and then Tab to see how WebStorm will handle auto-completing the line for you. The component exported by the module is now available for use in the variable Task, just as it was earlier.

Notice that when importing our own components, their location must be given in relation to the importing file:

'./components/Task.jsx'

The period (.) at the start refers to the current directory, so the module's location is a file called Task.jsx in the components sub-directory of the current directory. The filename extension .jsx can be omitted.

Modules have plenty of other uses other than enabling component declarations to be separated into their own files. We will get back to them later in this course.

The current code of the application can be found on GitHub.

Notice that the main branch of the repository is largely empty. The current code is in the branch part2-1:

GitHub branch screenshot

Remember: If you clone the project, run the command npm i before starting the application with npm run dev.

Using console.log

Important: for this section you may want to skip it and read it after you have started working on the exercises. You do not need to type this section into the reading folder. If you do, then please revert your changes once this section is complete.

Early in your programming career (and even after 25+ years of coding like yours truly), what can happen is that an application can just break down. This is even more so the case with dynamically typed languages, such as JavaScript, where the compiler does not check the data type. Like with function variables or return values.

A "React explosion" can, for example, look like this:

react sample error

In these situations, your best way out is to either debug the program via the debugger or commands like console.log.

The piece of code causing the explosion is this:

const Company = ({ company }) => (
  <div>
    <Header company={company} />
  </div>
)

const App = () => {
  const company = {
    name: "Sample Company",
  }

  return (
    <div>
      <Company company={company} />
    </div>
  )
}

We'll hone in on the reason for the breakdown by adding console.log commands to the code. Because the first thing to be rendered is the App component, it's worth putting a console.log in there. Use your clog live template that we created above to get some easy information in, since clog wants variables, type company and then Enter to type what you expect it to have. Then, press Enter again. For me, I did something like this.

const App = () => {
  const company = {
    // ...
  }

  console.log('File: App.js, Function: App, Line 24 - Is company (JS Object)?: ', company);
  return (
    // ..
  )
}

To see the printing in the console, we must scroll up over the long red wall of errors.

An initial printing of the console

When one thing is found to be working, it's time to dig deeper. If the component has been declared as a single statement or a function without a return, it makes printing to the console harder.

const Company = ({ company }) => (
  <div>
    <Header company={company} />
  </div>
)

The component should be changed to its longer form for us to add the printing:

const Company = ({ company }) => { 
  console.log(company)  return (
    <div>
      <Header company={company} />
    </div>
  )
}

Quite often the root of the problem is either:

  • the props are expected to be of a different type
  • the props are called with a different name than how they are passed in

So destructuring fails as a result. The problem often begins to solve itself when destructuring is removed and we see what the props contain.

const Company = (props) => {  console.log(props)  const { company } = props
  return (
    <div>
      <Header company={company} />
    </div>
  )
}

If the problem has still not been resolved, then I would consider digging deeper by leveraging WebStorm's powerful debugger that we covered in part 1. You can also get help from search tools and LLMs to help you understand the errors, just not to produce solutions.

As you continue through, please remember the pledge you took to help you improve as a web developer.

FYI: If you see errors in ESLint related to props validation and you are unsure what they are, you may want to go back and look at the explanation about those errors in part 1 Also: The original author added this chapter to the material after the model answer for the next question exploded completely (due to props being of the wrong type), and it had to be debugged using console.log.