0

I encounter the following question fairly often, but never really found a good duplicate target. Most of the time due to a lot of irrelevant overhead code. With this question I'm trying to craft a bare bones example that can be used as easily be used as duplicate target.

I've got an array of to-do items:

[
  { id: 1, task: "go to the grocery store", isDone: false },
  { id: 2, task: "walk the dog",            isDone: true  },
  { id: 3, task: "buy a present for John",  isDone: false },
]

It doesn't matter where this array comes from.

It could be hard-coded:

const todos = [
  { id: 1, task: "go to the grocery store", isDone: false },
  { id: 2, task: "walk the dog",            isDone: true  },
  { id: 3, task: "buy a present for John",  isDone: false },
];

It might come from a static file loaded on the server.

import todos from "../data/todos.json";

It might be the result of an web API call.

useEffect(() => {
  fetch("https://api.example.com/v1.0/me/todo-items")
    .then(response => response.json())
    .then(todos => setTodos(todos))
}, []);

It might be build by the end-user as part of the application logic.

function handleAddTodoSubmit(e) {
  e.preventDefault();
  const todo = { id: nextID, task, isDone };
  setTodos(todos => [...todos, todo]);
  setNextID(nextID => nextID + 1);
  setAddTodoDialogOpen(false);
}

No matter how todos is defined, the following does NOT render the to-do items:

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => {
        <li key={todo.id}>
          <pre>
            <ASCIICheckbox isChecked={todo.isDone} />
            {" "}
            {todo.task}
          </pre>
        </li>
      })}
    </ul>
  );
}

When I inspect the resulting structure, I can see that the <ul> element is present, but it doesn't contain any <li> elements. See the snippet down below to run this yourself.

const TODOS = [
  { id: 1, task: "go to the grocery store", isDone: false },
  { id: 2, task: "walk the dog",            isDone: true  },
  { id: 3, task: "buy a present for John",  isDone: false },
];

function ASCIICheckbox({ isChecked }) {
  const check = isChecked ? "x" : " ";

  return (
    <React.Fragment>[{check}]</React.Fragment>
  );
}

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => {
        <li key={todo.id}>
          <pre>
            <ASCIICheckbox isChecked={todo.isDone} />
            {" "}
            {todo.task}
          </pre>
        </li>
      })}
    </ul>
  );
}

function App() {
  return (
    <div>
      <h1>My todo list:</h1>
      <TodoList todos={TODOS} />
    </div>
  );
}

ReactDOM.createRoot(document.querySelector("#root"))
  .render(<React.StrictMode><App /></React.StrictMode>);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="root"></div>

I expect the 3 tasks to be rendered, but they are not.

How do I fix my render and display the items inside the collection?

3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
  • Though this question is marked as "duplicate". A lot of novice React programmers encounter this issue when working with JSX. Adding the additional React/JSX context might help people understand what the issue is, and how to solve it. – 3limin4t0r Jan 27 '23 at 17:58

1 Answers1

0

The reason why the to-do items aren't rendered is because the map() callback doesn't have a return value.

Let's first look at the arrow function documentation of MDN:

Function body

Arrow functions can have either a concise body or the usual block body.

In a concise body, only a single expression is specified, which becomes the implicit return value. In a block body, you must use an explicit return statement.

const func = (x) => x * x;
// concise body syntax, implied "return"

const func2 = (x, y) => {
  return x + y;
};
// with block body, explicit "return" needed

(todo) => {
  <li key={todo.id}>
    <pre>
      <ASCIICheckbox isChecked={todo.isDone} />
      {" "}
      {todo.task}
    </pre>
  </li>
}

Uses a block body, but doesn't have a return statement, therefore the return value is undefined.

We can solve this in multiple ways, the first one being simply adding the return statement to the callback.

(todo) => {
  return ( // <- notice the return
    <li key={todo.id}>
      <pre>
        <ASCIICheckbox isChecked={todo.isDone} />
        {" "}
        {todo.task}
      </pre>
    </li>
  );
}

const TODOS = [
  { id: 1, task: "go to the grocery store", isDone: false },
  { id: 2, task: "walk the dog",            isDone: true  },
  { id: 3, task: "buy a present for John",  isDone: false },
];

function ASCIICheckbox({ isChecked }) {
  const check = isChecked ? "x" : " ";

  return (
    <React.Fragment>[{check}]</React.Fragment>
  );
}

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => {
        return ( // <- notice the return
          <li key={todo.id}>
            <pre>
              <ASCIICheckbox isChecked={todo.isDone} />
              {" "}
              {todo.task}
            </pre>
          </li>
        );
      })}
    </ul>
  );
}

function App() {
  return (
    <div>
      <h1>My todo list:</h1>
      <TodoList todos={TODOS} />
    </div>
  );
}

ReactDOM.createRoot(document.querySelector("#root"))
  .render(<React.StrictMode><App /></React.StrictMode>);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="root"></div>

The second solution is changing the block body into a concise body. This can be done by removing the {/} block delimiters.

(todo) => ( // <- notice the change from `{` into `(`
  <li key={todo.id}>
    <pre>
      <ASCIICheckbox isChecked={todo.isDone} />
      {" "}
      {todo.task}
    </pre>
  </li>
)

const TODOS = [
  { id: 1, task: "go to the grocery store", isDone: false },
  { id: 2, task: "walk the dog",            isDone: true  },
  { id: 3, task: "buy a present for John",  isDone: false },
];

function ASCIICheckbox({ isChecked }) {
  const check = isChecked ? "x" : " ";

  return (
    <React.Fragment>[{check}]</React.Fragment>
  );
}

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => ( // <- notice the change from `{` into `(`
        <li key={todo.id}>
          <pre>
            <ASCIICheckbox isChecked={todo.isDone} />
            {" "}
            {todo.task}
          </pre>
        </li>
      ))}
    </ul>
  );
}

function App() {
  return (
    <div>
      <h1>My todo list:</h1>
      <TodoList todos={TODOS} />
    </div>
  );
}

ReactDOM.createRoot(document.querySelector("#root"))
  .render(<React.StrictMode><App /></React.StrictMode>);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="root"></div>

If you're having trouble understanding what happens in the second solution, let me explain the grouping operator (/) first. In React (specifically JSX) it's common to see normal parentheses in combination with an arrow function. However the feature can be explained using basic JavaScript.

The main reason for using them is so you can split up your arrow function with concise body into a multiline statement.

const doubles = nrs.map((nr) => nr * 2);

// could also be written as (notice the additional parentheses)
const doubles = nrs.map((nr) => (nr * 2));

// which can be split across lines
const doubles = nrs.map((nr) => (
  nr * 2 // <- now imagine this being a complex statement
));

Any expression can be placed in parentheses, it doesn't have to be in combination with an arrow function. const name = "John Doe" could be written as const name = ("John Doe") and yield the same result.

In the above example placing nr * 2 on a separate line is counter intuitive, because it's such a small expression. However the readability of JSX (which is one large expression) benefits a lot from being spread across multiple lines.

<ul>
  {nrs.map((nr) => <li>complex and long structure {nr * 2}</li>)}
</ul>

// could also be written as (notice the additional parentheses)
<ul>
  {nrs.map((nr) => (<li>complex and long structure {nr * 2}</li>))}
</ul>

// which can be split across lines
<ul>
  {nrs.map((nr) => (
    <li>
      complex and long structure {nr * 2}
    </li>
  ))}
</ul>
3limin4t0r
  • 19,353
  • 2
  • 31
  • 52