DEV Community

Cover image for Functions, fat arrows and parentheses
Laurie
Laurie

Posted on • Originally published at laurieontech.com

Functions, fat arrows and parentheses

If you develop with JavaScript you likely use functions fairly often. And, because you're a developer, you've likely made some mistakes.

For me, it was last week. I called a function without parentheses and it didn't exactly do what I wanted. But why? Why was that a mistake? In React there are lots of times we use functions without parentheses and everything works just fine!

Today we're going to talk about why.

How do parantheses impact functions

Let's start with a typical function.

const someString = () => {
  return 'some string'
}
Enter fullscreen mode Exit fullscreen mode

If we wanted to call this function, we'd do so like this.

const result = someString()
// result is now "some string"
Enter fullscreen mode Exit fullscreen mode

But what happens if we do this?

const result = someString
Enter fullscreen mode Exit fullscreen mode

result is now equal to [Function: someString]. It's a reference to the function rather than the result of evaluating the function.

Well that was a quick post. Always use parentheses, problem solved.

Not so fast!

React and functions

Sometimes in React we want to execute a function. But other times, we want to pass around a reference.

const ExampleComponent = () => {
  const clickHandler = () => {
    console.log('I was clicked')
  }

  return <button onClick={clickHandler}>Click me</button>
}
Enter fullscreen mode Exit fullscreen mode

onClick is an event handler which takes a function as a callback. So it needs a reference to the function it's going to call.

What happens if we add parantheses? Will it still work?

const ExampleComponent = () => {
  const clickHandler = () => {
    console.log('I was clicked')
  }

  return <button onClick={clickHandler()}>Click me</button>
}
Enter fullscreen mode Exit fullscreen mode

Nope! Nothing will get logged. The event handler was expecting a function that it can call. However, it got the return value of the function.

Any other syntax weirdness we should talk about? Sure, why not!

Parameters

By default, event is passed as an argument to the callback function. Something like this.

const ExampleComponent = () => {
  const clickHandler = event => {
    event.preventDefault()
    console.log('I was clicked')
  }

  return <button onClick={clickHandler}>Click me</button>
}
Enter fullscreen mode Exit fullscreen mode

This actually introduces an interesting detail! The code above is equivalent to the code below, passing our function wrapped in an anonymous function that exposes event.

const ExampleComponent = () => {
  const clickHandler = event => {
    event.preventDefault()
    console.log('I was clicked')
  }

  return <button onClick={event => clickHandler(event)}>Click me</button>
}
Enter fullscreen mode Exit fullscreen mode

Anonymous functions

As it turns out, we can define our function inline.

const ExampleComponent = () => (
  <button onClick={() => console.log('I was clicked')}>Click me</button>
)
Enter fullscreen mode Exit fullscreen mode

This also gives us the opportunity to pass our own parameters.

const ExampleComponent = () => {
  const clickHandler = message => {
    console.log(message)
  }

  return <button onClick={() => clickHandler('I was clicked')}>Click me</button>
}
Enter fullscreen mode Exit fullscreen mode

But what if we want the event object in addition to our other parameter(s)?

const ExampleComponent = () => {
  const clickHandler = message => event => {
    event.preventDefault()
    console.log(message)
  }

  return <button onClick={clickHandler('I was clicked')}>Click me</button>
}
Enter fullscreen mode Exit fullscreen mode

This makes sense if we think about what we already know. That event is always passed, whether we reference it or not.

I'm a little confused

If that last example confused you, that's ok! It looks a lot like our earlier example where we passed the result of a function rather than a reference to it.

The trick is to look at the definition of clickHandler a little bit closer. We'll make it a bit more verbose to make that easier.

const clickHandler = message => {
  return event => {
    event.preventDefault()
    console.log(message)
  }
}
Enter fullscreen mode Exit fullscreen mode

The "result" of clickHandler is a function! It returns a reference to a function. So we're all good.

Functions are fun

I know that was a lot of syntax, but I hope you feel a bit more confident. Knowing what is happening under the hood can turn guess and check errors into intentional fixes. You'll still make mistakes, we all do, but maybe you'll catch them faster.

Discussion (12)

The discussion has been locked. New comments can't be added.
Collapse
lionelrowe profile image
lionel-rowe • Edited on

Nice article! Couple of nitpicks, though:

const ExampleComponent = () => {
  const clickHandler = () => {
    console.log('I was clicked')
  }

  return <button onClick={clickHandler()}>Click me</button>
}

Nope! Nothing will get logged.

"I was clicked" will get logged each time the component is rendered, but nothing will be logged when the button is clicked. If a function is called, its side effects will still happen, even if it's called in an unintended way.

The event handler was expecting a function that it can call. ​However, it got "I was clicked" instead! Not exactly helpful.

onClick received undefined, not the string "I was clicked", because clickHandler doesn't return anything (implicitly returns undefined):

const noReturn = () => {
    console.log('...')
}

const x = noReturn() // logs '...'
console.log(typeof x) // 'undefined'
Enter fullscreen mode Exit fullscreen mode
Collapse
laurieontech profile image
Laurie Author

The point of the first piece is that nothing will get logged when the button is clicked because we’re talking about the behavior of the click handler. Discussing what happens when the component renders is outside the scope of the conversation.

The second example is a bit confusing. I wanted to match what happens if you execute that same thing in node and what response you see so that people can run it themselves, but it’s not a 1:1 mapping.

Collapse
lionelrowe profile image
lionel-rowe • Edited on

Yeah you'll get the logging output, but that's not the same as the return value. The return value is always undefined unless a) an explicit return statement is used to return something other than undefined, or b) it's a single-statement arrow function with no enclosing block:

const fn1 = () => { 5 } // returns undefined
const fn2 = function() { 5 } // returns undefined
const fn3 = () => { return 5 } // returns 5
const fn4 = () => 5 // returns 5
const fn5 = function() { return 5 } // returns 5
Enter fullscreen mode Exit fullscreen mode

In the case of console.log, even if you return console.log('...'), the return value will still be undefined, because console.log itself returns undefined.

Sorry if I'm stating the obvious or my explanation is confusing.

Collapse
thumbone profile image
Bernd Wechner • Edited on

I have to admit I find it odd that you start with a "typical function":

const someString = () => {
  return 'some string'
}
Enter fullscreen mode Exit fullscreen mode

when that is bizzaro to me and not typical. Sure I see it a fair bit nowadays, it's not rare per se, but it is still distinctly a neologism and maybe it's old school but this is a typical function:

function someString() {
  return 'some string'
}
Enter fullscreen mode Exit fullscreen mode

Which, hey, is reminiscent of the banner image ;-).

One of the most fun things about functions though that you've missed IMHO is the loss of this, most especially in event handlers. It's also not uncommon to use classes as a means of encapsulating and isolating closely related code. And when your event handlers are methods of a class or functions inside of methods even, this becomes a fairly hefty issue.

To wit, we commonly see .bind(this) which is one of Javascript's painful little idiosyncrasies πŸ˜‰

Collapse
tanth1993 profile image
tanth1993

the last example is called currying function.
using with shorthand - a reference to function saves memory but this is unclean and a bit hard to debug when we use currying function

Collapse
boaty profile image
Thiraphat-DEV

nice

Collapse
jonrandy profile image
Comment marked as low quality/non-constructive by the community. View Code of Conduct
Info Comment hidden by post author - thread only accessible via permalink
Jon Randy • Edited on

For me, it was last week. I called a function without parentheses and it didn't exactly do what I wanted. But why? Why was that a mistake? In React there are lots of times we use functions without parentheses and everything works just fine!

I have to say that for a 'Senior Software Engineer at Netflix' - this is somewhat worrying πŸ˜›

Collapse
lukeshiru profile image
LUKESHIRU

What is worrying is that you believe that a "Senior software engineer" can't make this kind of mistake. FYI, Senior devs are still human beings, that still need to Google stuff, and still make mistakes...... and I'm still wondering what does Netflix have to do with anything mentioned in this post :/

Collapse
jonrandy profile image
Comment marked as low quality/non-constructive by the community. View Code of Conduct
Jon Randy • Edited on

Mistakes like knowing the difference between calling a function and referring to a function? That is really basic

lukeshiru profile image
LUKESHIRU

So .... Im guessing you never trip on anything ... right? .... I mean is just walking .... is REALLY basic........... 🀦

Collapse
rounakcodes profile image
rounakcodes

In my view, you were actually being very kind by mentioning just one designation. Unfortunate, that it still got marked as low quality/non-constructive by this site.

Some comments have been hidden by the post's author - find out more