60

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:

export default function foo() {
  return ...;
}

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

etoxin
  • 4,908
  • 3
  • 38
  • 50
Philip Johnson
  • 1,463
  • 2
  • 11
  • 20
  • 52
    It looks a lot cooler – Dan Mandel Mar 15 '18 at 17:52
  • 1
    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
  • 22
    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/ – Dom Mar 15 '18 at 18:03
  • 5
    It's worth mentioning that the [official React documentation](https://reactjs.org/docs/components-and-props.html) uses the traditional `function`. – rodrigocfd Jan 10 '22 at 03:26
  • @DanMandel No it doesn't look cooler, it's annoying. – basickarl Feb 28 '23 at 15:51

5 Answers5

24

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.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
dfsq
  • 191,768
  • 25
  • 236
  • 258
  • 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
  • 1
    @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
  • 1
    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
  • 2
    @IvanKleshnin Component definitions do not need to be concise. If anything the preference should be clarity over brevity. Who is writing 1-2 line functional comps (at all) and then feeling good about it b/c of an implicit return? People do it b/c FB does it and ESLint defaults it. Try reading an ES6 module where *everything* is defined as a const...The distinction is valuable. Not to mention that the method signatures actually take *more* key strokes and are inherently busier because of the = and =>. Arrow notation is a feature and should be used with intention. – GHOST-34 Dec 24 '21 at 01:41
24

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 the 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 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 are defined in the same way, so definitely there is no difference between them and it is fully opinion based on 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
  • 29,059
  • 15
  • 130
  • 154
  • Until you run into some issues, and spend a whole day trying to find the mistake just to realise you had to use normal functions instead of arrow ones. They are not the same, arrow functions break the jsx flow – ErayZaxy May 25 '23 at 14:00
10

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
  • 795
  • 8
  • 11
  • You could argue that because ES6 implements so many arrow functions `.map / .filter / .reduce`, writing named functions would introduce inconsistency. A weak argument, an argument nonetheless. – Walter Monecke May 25 '22 at 19:59
5

A few other points not mentioned in other answers:

  • With arrow function components, when using React dev tools in Chrome/Firefox, those components come up as Anonymous making debugging harder. These Anonymous components are also throughout dev tools including performance flame trees. Functional components display their name in dev tools.
  • A standard function declaration can be defined on a single line. You don't need to define the export default later in a file. This also makes it easier when you want to add/remove the default keyword.
export default async function MyComponent() {
  ...
}
etoxin
  • 4,908
  • 3
  • 38
  • 50
3

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
  • 29,059
  • 15
  • 130
  • 154
Abslen Char
  • 3,071
  • 3
  • 15
  • 29
  • 15
    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
  • 20
    I'm going to disagree that it's "easier to read". We read left to right, top to bottom and the usage of `function` makes it easy to identify declarations that are functions versus declarations that are variables without having to scan right visually. I absolutely hate reading files that are straight `const` down the left because it requires scanning right to determine if it's a function or a variable. Using the `function` keyword makes the code easier to scan top to bottom without scanning left to right. – Charles Chen Oct 30 '21 at 20:36
  • @CharlesChen Have worked on big projects where arrow or named functions where used as standard. Arrow functions are easier for me to recognise (not read). I think this is purely an opinion and everyone will prefer one over the other. Cheers! – Walter Monecke May 25 '22 at 19:58
  • 2
    "compactness" seems like the wrong term. `export const Component = () => {…};` is actually longer than `export function Component() {…}` – Bergi Oct 10 '22 at 03:53
  • 1
    1. This "safety" you are talking about, is only useful for methods (functions that are called from an object). Functional components are not dependent on an object, and the "this" keyword belongs to the scope of the module itself, which is going to be undefined or pointing to the window or document object (I'm not sure how React and Babel define it). 2. I think reading from left to right using the function word is actually easier to read. Arrow functions add an extra layer of syntax complexity. I will agree though that arrow functions are much much "cooler". – babaliaris Dec 24 '22 at 13:12