18

I always see examples of functional React components defined with arrow function syntax:

const foo = () => (...);

export default foo;

Rather than the more traditional function declaration syntax:

function foo() {
  return ...;
}

export default foo;

Is there a reason to prefer the former over the latter?

AmerllicA
  • 15,720
  • 11
  • 72
  • 103
Philip Johnson
  • 958
  • 7
  • 20
  • 21
    It looks a lot cooler – Dan Mandel Mar 15 '18 at 17:52
  • Citation needed :) I think this is opinion based, we just might be talking about personal preferences of developers. – Kos Mar 15 '18 at 17:53
  • 7
    This is one of the those bad practices that got popularased on the hype. React comunity started to do it, and everybody suddenly followed. Even though there are several reasons why it should be considered anti-pattern and normal function declaration should be preferred. – dfsq Mar 15 '18 at 17:55
  • Check out this article: https://www.sitepoint.com/es6-arrow-functions-new-fat-concise-syntax-javascript/ – Dominik Janković Mar 15 '18 at 18:03

4 Answers4

12

I would say that this is a bit opinionated choice really. There are at least several reasons why I (personally) see arrow function use for a purely functional component as pretty bad practice. Here are those:

  1. Syntax abuse. When we define function component we don't need to pre-bind its context to a specific scope. The context (this) is going to be undefined anyway in the module namespace. The use of arrow functions is dictated here by pure aesthetics reasons like conciseness. But arrow functions as language feature has a very specific purpose for existence in the first place, and this is not coolness and conciseness.

  2. Error stack trace. Exceptions thrown in arrow function will be less descriptive because arrow function is anonymous by definition. This is not the huge problem probably since React project will most likely be configured with proper source maps support, but still stack trace will be a bit more clear if named function is used. As noted in comments this is not really an issue of the functional component, as the name will be the name of the variable basically.

  3. Less convenient logging. Consider this very typical pure function component style:

    const Header = ({ name, branding }) => (
      <header>
        ...
      </header>
    )
    

In the function above it's impossible to throw in quick debugger statement or console.log. You will have to temporarily convert it to something like this

const Header = function ({ name, branding }) { 
  console.log(name)
  return (
    <header>
      ...
    </header>
  )
}

This might be pretty annoying especially for bigger pure functional components.

That being said this is a very popular choice for many teams, also by default preferred by ESLint, so if you don't see the problem with it, then it is probably okay.

AmerllicA
  • 15,720
  • 11
  • 72
  • 103
dfsq
  • 182,609
  • 24
  • 222
  • 242
  • I'll contest #2 in the case of this specific example. That is an issue in some cases, but here it will pick up the name from the assigned binding: https://stackoverflow.com/questions/32828698/difference-between-anonymous-function-vs-named-function-as-value-for-an-object-k/32830772#32830772 – loganfsmyth Mar 15 '18 at 18:20
  • @loganfsmyth you are absolutely correct. Then point #2 is not a not an issue indeed, I think I confused the problem with the use of arrow functions in callback, like map, etc. – dfsq Mar 15 '18 at 18:27
  • Yeah, definitely an annoyance for callbacks :( – loganfsmyth Mar 15 '18 at 18:30
  • @dfsq 3 isn't much applicable, too. It's certainly more convenient to do this for multi-line function (temp variables help a lot), but you can always throw `console.log(),` or `eval('debugger'),` into one-line arrow (notice that React function components already have `(...)` for that. Also, it's not about regular vs arrow functions, it's about implicit vs explicit return. But important thing about debugging is that you have `arguments` in regular function (but arrow will have it in ES5 target, too). – Estus Flask Mar 15 '18 at 18:41
  • @estus I see what you mean, but I see arrow functions as the source of the problem anyway because if team uses arrow functions for components then likely that they will face problem with implicit returns too. – dfsq Mar 15 '18 at 18:53
  • 3 isn't really a valid concern - you could just [configure eslint](https://eslint.org/docs/rules/arrow-body-style) to require explicit return and curly braces ``` const Header = ({ name, branding }) => { console.log(name); return (
    ...
    ); } ```
    – wmp224 Jan 23 '19 at 16:00
  • @wmp224 so how are you going to log `name` to inspect? – dfsq Jan 23 '19 at 16:01
  • @wmp224 this is exactly what i wrote in my answer :) in order to log anything you have to make few shenanigans and convert arrow function to normal function. and then back again, once you are done with logging. – dfsq Jan 23 '19 at 16:05
  • 1
    @wmp224 oh, i see, we were talking about different things. of course you can. i was talking about short return notation. – dfsq Jan 23 '19 at 16:06
  • How can you call it "bad practice" if the reasons are just some "custom cases" easily changable like the third point when the reason because you cannot log straight forward it is only because you use the shorted return syntax? – quirimmo Oct 26 '19 at 16:58
  • Dowvoted for #1 "syntax abuse". Conciseness WAS one of the key reasons to add arrow functions to ES according to its developers. – Ivan Kleshnin Dec 18 '20 at 13:45
6

Actually, there is no difference between them, I make a little project on the CodeSandBox and make two simple components, one of them is the Arrow component by using arrow function:

import React from 'react';

const MyArrowComponent = () => (
  <main>
    <h2>Arrow</h2>
  </main>
);

export default MyArrowComponent;

And the other is the Declaration component by using function declaration:

import React from "react";

function MyFunctionComponent() {
    return (
        <main>
            <h2>Declaration</h2>
        </main>
    );
}

export default MyFunctionComponent;

Then I run the yarn build command and the got the bundle like below:

(window.webpackJsonp = window.webpackJsonp || []).push([[0], {
  14: function (e, n, t) {
    "use strict";
    t.r(n);
    var a = t(0), r = t.n(a), l = t(2),
        c = t.n(l), u = t(3), i = t(4), o = t(6), m = t(5), E = t(7);
    var p = function () {
      return r.a.createElement("main", null, r.a.createElement("h2", null, "Declaration"))
    }, s = function () {
      return r.a.createElement("main", null, r.a.createElement("h2", null, "Arrow"))
    }, d = function (e) {
      function n() {
            return (
              Object(u.a)(this, n),
              Object(o.a)(this, Object(m.a)(n).apply(this, arguments))
      }
      return Object(E.a)(n, e), Object(i.a)(n, [{
        key: "render", value: function () {
          return r.a.createElement(
            'div',
            null,
            r.a.createElement('div', null, 'Hi'),
            r.a.createElement(p, null),
            r.a.createElement(s, null)
          );
        }
      }]), n
    }(r.a.Component);
    c.a.render(r.a.createElement(d, null), document.getElementById("root"))
  }, 8: function (e, n, t) {
    e.exports = t(14)
  }
}, [[8, 1, 2]]]);

Pay attention to the definition of the Arrow and the Declaration component:

var p = function () {
  return r.a.createElement("main", null, r.a.createElement("h2", null, "Declaration"))
}, s = function () {
  return r.a.createElement("main", null, r.a.createElement("h2", null, "Arrow"))
}

Both of them defined in the same way, so definitely there is no difference between them and it is fully opinion based for developers' attitude to code readability and clean code, based on ESLint 5.x in our team we choose the arrow function to define the functional components.

AmerllicA
  • 15,720
  • 11
  • 72
  • 103
4

Using Arrow function is way better than using a regular function not only because the syntax is clean and you will be able to write less code with the arrow function but also because of :

  1. Scope safety: when arrow functions are used consistently, everything is guaranteed to use the same thisObject as the root. If even a single standard function callback is mixed in with a bunch of arrow functions there's a chance the scope will become messed up.

  2. Compactness: Arrow functions are easier to read and write.

  3. Clarity: When almost everything is an arrow function, any regular function immediately sticks out for defining the scope. A developer can always look up the next-higher function statement to see what this object is.

For more details, you can take a look at these questions

When should I use Arrow functions in ECMAScript 6?

AmerllicA
  • 15,720
  • 11
  • 72
  • 103
Abslen Char
  • 2,951
  • 3
  • 13
  • 29
  • 3
    The `this` binding is irrelevant in functions that are not attached to objects. – Jimmy Breck-McKye Nov 23 '18 at 11:16
  • To clarify - credit where credit is due: The 3 points listed above are from [lyschoening's answer mentioned in the question link above](https://stackoverflow.com/a/23045200/199364). – ToolmakerSteve Nov 02 '19 at 19:34
0

Function declaration and arrow functions are different in their essence, but in the scope of your question, it's basically a code style preference. I personally prefer Function declaration as I find it easier to spot the meaning of that line of code.

If you will use Arrow functions or Function declarations, try to also think in terms of what makes more sense in the context. To make the code clean and easier to read it isn't only about the amount of code you write but what that code express.

I tend to use Arrow Functions for callbacks, for example, [].map(() => {})

Fernando Souza
  • 400
  • 3
  • 6