La keyword THIS in React

Immaginiamo ora di voler settare una qualche proprietà dello stato al click del bottone. L’esempio sopra diventa:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {event: '-'};
  }
  handleClick(e) {
    this.setState({event: e.type});
  }
  render() {
    return (
      <div>
        <p>Tipo di evento: {this.state.event}</p>
        {/* Errore! Non fatelo a casa :) */}
        <button onClick={this.handleClick} >Pulsante</button>  
      </div>
    )
  }
}

Nella console apparirà un messaggio di errore “Cannot read property ‘setState’ of null“:

Il riferimento “this” è null.  Questo tipo di errore non è legato a  React, ma è  Javascript. Per trovare la causa dell’errore facciamo qualche esempio in Javascript.

"use strict";

function foo(){
  return this;
}

console.log(foo() === undefined); // true


class Baz {
    constructor() {
      console.log("Istanza di Baz creata");
      this.prop = "Baz prop";
    }
    bazFunc() {
      console.log(this.prop);
    }
    receiveCallback(callback) {
      console.log("receiveCallback called");
      // invochiamo la callback ricevuta come parametro
      callback();
    }
}

class Bat {
    constructor() {
      console.log("Istanza di Bat creata");
      this.prop = "Bat prop";
    }
    batFunc() {
      console.log(this.prop);
    }
}

const baz = new Baz();
const bat = new Bat();

baz.bazFunc() // "Baz prop"
bat.batFunc() // "Bat prop"

// 'this' sarà undefined
baz.receiveCallback(bat.batFunc); // Errore!

Notiamo che, quando invochiamo la funzione foo(), this sarà pari a ‘undefined’. Inoltre, nell’ultimo rigo, quando chiamiamo baz.receiveCallback() e passiamo un riferimento al metodo bat.batFunc, riceveremo il seguente errore nella console degli strumenti per sviluppatori.

Ossia, quando invochiamo callback(), ed eseguiamo il corpo della funzione batFunc(), this è ‘undefined’.  Questo perche, il valore di this è determinato da come la funzione viene invocata, non è come java in cui this è legato alla classe in cui è definita la funzione.

Per “legare” this al contesto in cui è definito il metodo, similmente a java, dobbiamo usare le arrow function.

Riscrivendo l’esempio  precedente avremo:

"use strict";

const globalObject = this;

const foo = () => this;

// globalObject === window nel browser
console.log(foo() === globalObject); // true


class Baz {
  constructor(bat) {
    this.prop = "Baz prop";
    this.bat = bat;
  }
  receiveCallback(callback) {
    const callbackResult = callback();
    console.log(
      "this.bat === callbackResult? ", 
      this.bat === callbackResult
    ); // true nel caso dell'arrow function
  }
}

class Bat {
  constructor() {
    this.prop = "Bat prop";
  }
  normalFunc() {
    return function() {
      console.log(this.prop);
    }
  }
  arrowFunc() {
    return () => {
      console.dir("L'arrow function restituita da arrowFunc() ha accesso a: " + this.prop);
      return this;
    }
  }
}

const bat = new Bat();
const baz = new Baz(bat);

// NOTA: stiamo invocando la funzione arrowFunc()
// 'this' punta all'oggetto bat
baz.receiveCallback(bat.arrowFunc()); // "Bat prop"


// NOTA: stiamo invocando la funzione normalFunc()
// 'this' sarà undefined
baz.receiveCallback(bat.normalFunc()); // Error! Cannot read property 'prop' of undefined

Ritornando su React, potremmo quindi usare delle arrow function come event handler all’interno dei nostri componenti

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {event: '-'};
  }
  handleClick(e) {
    this.setState({event: e.type});
  }
  render() {
    return (
      <div>
        <p>Tipo di evento: {this.state.event}</p>
        <button onClick={(e) => this.handleClick(e)} >Pulsante</button>  
      </div>
    )
  }
}

Con questa sintassi però viene creata una callback diversa  ad ogni render. Nella maggior parte dei casi, non vi sono problemi. Tuttavia, se questa callback viene passata come prop a componenti inferiori, tali componenti potrebbero eseguire un ulteriore re-renderizzamento, causando un problema di prestazioni. Per risolvere tutte queste problematiche, si usa la funzione Function.prototype.bind().

Esempio:

bFoo = foo.bind(newThis);
bFoo()

bFoo, grazie alla funzione Function.prototype.bind,  sarà  una copia di foo, ma il valore di this, all’interno di bFoo, sarà uguale al valore di newThis (il primo argomento della funzione bind.

Ritornando al nostro esempio, all’interno del costruttore useremo Function.prototype.bind sulla funzione this.handleClick

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {event: '-'};
    // usiamo Function.prototype.bind() all'interno del costruttore
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick(e) {
    this.setState({event: e.type});
  }
  render() {
    return (
      <div>
        <p>Tipo di evento: {this.state.event}</p>
        {/* Passiamo un riferimento alla funzione this.handleClick */}
        <button onClick={this.handleClick} >Pulsante</button>  
      </div>
    )
  }
}