Search code examples
javascriptarrow-functions

Trying to understand Javascript arrow functions


So I've got an open source library I am trying to extend.

It has the following values:

export const withX = fn => e => fn(getEvent(e).pageX);
export const getEvent = e => (e.touches ? e.touches[0] : e);

and inside a class, there is an implementation of how the original author intended it to work:

...

  onDragStart = withX(start => {
    if (this.state.swiped) return;

    this.setState({ start, pristine: false, moving: true });
  });

I want to change it to something like this (pseudocode):

onDragStart() {
let startX = withX(return start);
}

But I'm not entirely sure how to do it.

When I try something like this:

let startX = withX();

or this:

let startX = withX(start => startX);

All I get is the actual function return.

Can anyone explain arrow functions enough to me that I get what's going on here? Is there any way to get the start value here?

One of the answers that was deleted had the following solution:

  onDragStart(event) {
    let startX = withX(_ => startX)(event);
    console.log(startX);
  }

Unfortunately, this lead to undefined


Solution

  • Arrow functions are just syntactic sugar for shorthand functions. As a result of being shorthand, they also come with some small limitations such as this binding.

    If you are struggling with them, the first thing to do mentally is to rearrange the arrow function into its normal function form so that it may be easier to visualize. As you progress using them this should just become second nature.

    export const withX = fn => e => fn(getEvent(e).pageX);
    export const getEvent = e => (e.touches ? e.touches[0] : e);
    
    // Note here that the withX function is actually returning a function that
    // is expecting to be called with e as the argument (e generally being an event)
    function withX(fn){
        return function(e){
            return fn(getEvent(e).pageX);
        };
    }
    
    // This function seems to return the first touch event (generally related to converting
    // click events for mobile)
    function getEvent(e){
        if(e.touches) return e.touches[0];
        return e;
    }
    

    Now that you have seen this conversion, hopefully it makes more sense with how to incorporate in other classical implementations that you are more familiar with.

    For example, in the demo you provide,

    onDragStart = withX(start => {
        if (this.state.swiped) return;
    
        this.setState({ start, pristine: false, moving: true });
    });
    

    would then need to convert to

    var onDragStart = withX(function(start){
        if(this.state.swiped) return;
    
        this.setState({ start, pristine: false, moving: true });
    }.bind(this));
    

    The .bind(this) is there because inside of the function being sent, a new this binding will be created. However, since arrow functions do not do that (as part of their syntactic sugar) it wasn't originally required.

    In this code conversion, it should also be more clear that start is an injected value, and according to the above translation, you can see that start is the pageX value being injected into the callback function. As start is scoped to the inner function during injection, it would need to be extracted from the inner scope.

    Also, if you look at the above translation, you can see that the value returned from the arrow function is the value returned from the callback function. The callback function fn is injected with the start value, and as a result, returning the start injected value from the callback function fn will return that value at the end of the call.

    Option 1: directly return the value you want in the original implementation

    onDragStart = withX(start => {
      if (this.state.swiped) return;
    
      this.setState({ start, pristine: false, moving: true });
    
      // instead of onDragStart being undefined, it will now hold the start variable
      return start;
    });
    

    Option 2: store the variable during implementation

    var startX;
    onDragStart = withX(start => {
      if (this.state.swiped) return;
    
      this.setState({ start, pristine: false, moving: true });
    
      // now, assuming state.swiped was true, startX will hold the pageX value
      // onDragStart will still end up being undefined in this implementation
      startX = start;
    });
    

    Option 3: convert one of these versions to a normal function syntax and/or use a different data structure for storage.