4

This question is intended as canonical duplicate target for questions about problems that stem from confusing the single-line/expression arrow function body syntax with automatic return with their multi-line/block version.


I have an arrow function to add two numbers, but when I call it, it returns undefined. Why?

const add = (a, b) => {
  a + b
}

console.log(add(1, 2)) // expected: 3, actually: undefined

Alternative question:

My React component is supposed to render list items using map, but the list stays empty. Why?

<ul>
  {list.map(item => {
    <li>
      <a href="{item.url}">{item.name}</a>
    </li>
  })}
</ul>
FZs
  • 16,581
  • 13
  • 41
  • 50
CherryDT
  • 25,571
  • 5
  • 49
  • 74
  • I swear, I searched for over 10 minutes and couldn't find that [dup](https://stackoverflow.com/questions/28889450/when-should-i-use-a-return-statement-in-es6-arrow-functions) Thanks – CherryDT Sep 14 '22 at 16:11

2 Answers2

5

Arrow functions support two different styles of bodies: expressions and blocks.

If a single expression is provided (e.g. a + b) without braces { } around it, that expression is automatically returned:

const add = (a, b) => a + b

If a block enclosed by { } is provided, it works like a regular function body and requires a dedicated return statement to return a value:

const add = (a, b) => {
  return a + b
}

Single-expression bodies are often used to write simple functions in a concise way which execute one operation or condition, as in the following examples:

if (users.every(user => user.age >= 18)) { /* ... */ }

const emails = users.map(user => user.email)

const titleCased = string.replace(/\b\w/g, s => s.toUpperCase())

// in the following example, the return value is irrelevant
setTimeout(() => doStuff(1, 2, 3), 1000)

In most other cases, especially if you want to have multiple statements, loops or conditions in your function body, a block is used.

Note that you can have a function body spanning multiple lines even without a block if it is still a single expression, but if you would like to turn it into a block for readability reasons, you must not forget to add return (unless your function isn't supposed to return anything).

Now, this is the reason why your add function is returning undefined - it neither has a single-expression body (the { } make it a block) nor does it have any return statement in its body. So, what happens is that a + b is evaluated, but the result isn't used for anything - it is thrown away and execution continues, reaching the end of the function and returning without any return value since none was given, i.e. returning undefined.


In the React case, the problem is the same. You are embedding the return value of a .map call, which should be an array of further content to render, but because your callback is not returning any value, you are mapping the items to several undefined values and rendering that at the end.

There is another twist here though: you may often need multiple lines in the element(s) that you return from a function like a map callback, but you will find that neither of the following two options looks quite clean:

<ul>
  {list.map(item => <li>
    <a href="{item.url}">{item.name}</a>
  </li>)}
</ul>

<ul>
  {list.map(item => {
    return <li>
      <a href="{item.url}">{item.name}</a>
    </li>
  })}
</ul>

Instead, what is usually done is enclosing the expression in parentheses ( ). It is a still a single expression at the end, avoiding the need for an extra return statement, but is a lot nicer to work with:

<ul>
  {list.map(item => (
    <li>
      <a href="{item.url}">{item.name}</a>
    </li>
  ))}
</ul>

For more information about arrow functions in general, see here. To read about other differences between arrow functions and regular functions (such as different behavior of this), see here.

FZs
  • 16,581
  • 13
  • 41
  • 50
CherryDT
  • 25,571
  • 5
  • 49
  • 74
0

Arrow functions return code in these cases:

Case 1: When it's written inline like below

/* notice that this is an implicit return 
and we don't need a return statement here as the code is 
in the same line after the => */

const add = (a, b) => a + b  
    
console.log(add(1, 2)) // expected: 3

Case 2: When it's written with round brackets () like this

Case 2 - example 1

/* notice the round bracket here. 
You will use this when you have a 
block of code here unlike case 1 
where you had a single line to return */

const add = (a, b) => ( // this is round bracket, no return needed 
    a + b 
    // and other blocks of code. Look at the below JSX example for
    // for this case
)

console.log(add(1, 2)) // expected: 3

The above example is similar to first case 2 - example 1, but this case is more suitable for a single block of code mostly for JSX like below

Case 2 - example 2

<ul>
  {list.map(item => ( // this is round bracket, no return needed
    <li>
      <a href="{item.url}">{item.name}</a>
    </li>
  )}
</ul>

Case 3: With an explicit return statement when you use curly braces like this

const add3 = (a, b) => { // curly braces + return statement
  return a + b;
};

const res3 = add3(1, 2);

console.log(res3); // 3

In your case, notice you're mixing both cases 2 and 3. Meaning, you are using curly braces as defined in case 3 and also not using return keyword then like in case 2 which is the reason it doesn't work.

Code : https://codesandbox.io/s/javascript-forked-ckjg69?file=/src/index.js

Sandeep Amarnath
  • 5,463
  • 3
  • 33
  • 43
  • 2
    Your explanation of case 2 is a bit confusing because you can _not_ put a regular "block of code" inside of that consisting of any statements or more than one expression, it has to be still a single expression. It is in fact the same as case 1, the expression just being `( a + b )` now. (You could use the comma operator but then things quickly get ugly imo.) I also suggest splitting your code comments into multiple lines, they are currently a bit hard to read as they cause horizontal scrollbars. – CherryDT Sep 14 '22 at 16:27
  • Updated my answer! Let me know if you need more clarity. – Sandeep Amarnath Sep 14 '22 at 18:36