Return statement is ok
Tags: javascript, webdev, codequality, elm
Published: 2021-10-09 11:26:48

--- series: It is ok ---

Hey, long time no see. I have a new fancy subject, very much related to the code style, so the subject which developers like to argue about. My motivation to write this article is the eslint rule which I've lately found in the codebase, exactly - [arrow-body-style](https://eslint.org/docs/2.0.0/rules/arrow-body-style). So we will talk about - using or not using "return" statements in expression based arrow functions.

![dev argue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kxhrbtfs1q4idpmoytzv.png)

## Arguments against explicit return
Few points which will tell us why we have this rant at all.

#### Explicit return is less readable.
JS has return statement, yes it does, even though features like [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) allow for not using it, we cannot magically make JS a language without it. Why? Because we have statements, and when we need to use them, we need to use return statements also.

Consider below two code examples:
```js
let hateReturn = (user) => (
  hasDog(user) ? "Dog lover" : "Cat lover";
)

let returnIsOk = (user) => {
  return hasDog(user) ? "Dog lover" : "Cat lover";
}
```
Is there any difference between them in terms of readability? I don't think there is any difference. Do you think "return" very much makes this code worse? I don't think so. Do you think the second form should be somehow forbidden? I hope not.

#### Arrow functions should be consistent and not use return
Yes, great but is it even possible? Let's add another function.
```js
let hateReturn = (user) => (
  hasDog(user) ? "Dog lover" : "Cat lover";
)

let returnOk = (user) => {
  return hasDog(user) ? "Dog lover" : "Cat lover";
}

let anotherReturnOk = (user, max) => {
  const distance = calculateDistance(user);
  if (distance > max) {
    return "Too far";
  } else {
    return "Close enough";
  }
}
```
How do you see these three functions together? Which ones are consistent in syntax? Looks like last two have the same brackets, the same form of returning, and the *hateReturn* has no curly brackets but round ones, it has also no return.

There is no chance that all your functions will not have a return statement. And for sure having some of them with curly brackets and returns, and some of them with round ones doesn't make the code consistent. 

> **Note** Yes you can ho hardcore and make you own language by creating all statements as expressions, but we don't talk about such ;)

> **Note** Yes we can use standard [function declaration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function) and not be bothered by arrow functions. Then there is no choice to make.

#### With "return" there is more code
Yes it is, so what?

#### Arrows without return are cool 
Yes, they are...
![fancy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/06iegsyvjovmzcbd011f.png)
<hr>
## Arguments for explicit return
Now counter attack. Beware.

#### Flexibility issues with implicit return
![Flexibility](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nu3zcce1v1ok1x0phn2b.png)
What if our function needs to have additional logic included? Ye we can try to keep implicit return by keeping conditional expression:

```js
let hateReturn = (user) => (
  hasDog(user) 
    ? "Dog lover" 
    : hasCat(user) 
      ? "Cat lover"
      : "No animals"
)
```
Take a look at the formatting which even better allows us to read this. Is it bad?  I don't think it is, but what if I would like to make it more explicit with "if" instead of ternary? (Un)fortunately we need to rewrite the function and use explicit return then:
```js
let returnIsOk = (user) => {
  if (hasDog(user)) {
    return "Dog lover"
  } 
  if (hasCat(user) {
    return "Cat lover"
  }
  return "No animals"
}
```
We used a technique called "early return" here. And again is the second version the worst? I don't think so. And it is not a rant aiming into ternaries, no, it only shows that we are less flexible with implicit returns, and we need to do a lot of gymnastics to keep it.

#### Additional variables
Assignment is a statement, therefore you cannot use it in arrow function without return. Consider two code examples:

```js
let hateReturn = (user) => (
  user.dogs + user.cats > 5 && user.flat_size < 50 
    ? "Cannot have more animals" 
    : "Can have more animals"
)
```

```js
let returnIsOk = (user) => {
  const manyAnimals = user.dogs + user.cats > 5;
  const smallFlat = user.flat_size < 50;

  if (manyAnimals && smallFlat) {
    return "Cannot have more animals"
  } else {
    return "Can have more animals"
  }
}
```
Unfortunately any local variables for better readability cannot happen in the implicit return version.

> **Note** Expression based languages like Elm, Haskell have special expression for having local constants. In that way we can use expression and still make code more readable. Below example from Elm:

```elm
-- ELM LANGUAGE --
let
    manyAnimals = 
      user.dogs + user.cats > 5
    smallFlat =
      user.flat_size < 50
in
  if manyAnimals && smallFlat then 
    "Cannot have more animals"
  else
    "Can have more animals"
```
And it is an expression, there is also the if expression visible above. No statements but code looks almost the same as the one with statements. Also no explicit return, but this is not JS and there is no return keyword.

#### Do you return or not dilemma
Two functions close to each other

```js
let bomb = (bomb) => {
  api.post.sendBomb(bomb); // doesn't return
}

let secondBomb = (bomb) => (
  api.post.sendBomb(bomb); // does return
)
```
Looks like no difference, but because of different brackets we have different returns, first function returns void, second returns what `sendBomb` is returning. Quite an implicit difference don't you think? You need to look at brackets to understand if there is return or not.

#### Object returning
That is I think a legendary example, my first impression years ago was - what a hack, they introduced new syntax and created such an issue. How do we return objects by implicit return?

```js
let f = (a) => {a} // this has no return
let f2 = (a) => ({a}) // returns object with key 'a'
```
That always made me sad. Syntax confusion is high, therefore I see no issue in doing:
```js
let f3 = (a) => { 
  return {a}; 
}
```
Yes the longest of them all, but there is no confusion.

#### I want to use hooks in React

```jsx
const Component = ({name}) => (
  <div>
    {name}
  </div>
)
```
Great, now what if we need to use some hook inside? Yes, unfortunately we need to rewrite the function:

```jsx
const Component = ({name}) => {
  const [show, setShow] = useState(true);
  return {show && <div>
    <button onClick={() => setShow(true)} >Hide</button>
    {name}
  </div>}
}
```
There is no way to avoid explicit return. We need to rewrite the component, change brackets, add return. Maybe not much, but for me it is always a burden.

But wait, `onClick` there is an arrow function, it has no explicit return, there you go, you have used it - **looser**. Yep I am using implicit returns of arrow functions when I see it as a best fit, for example for function arguments in many cases we already have a closure with all needed data, we don't need any local variables, in most they are simple functions. So yes I see no issue in using implicit return, but the whole rant is not about not using it, but about forbidding using the return for expression based functions. I see no valid reason to forbid it, in the same way I see no reasons to not use standard function syntax.

In summary if you have **arrow-body-style** rule in your project, turn it off. Don't create not needed code restrictions.

> **Note** that the whole article is about JS only. If language does not have explicit return - good, there is no problem and no decision making.