back to top

Componenti React e l’oggetto Props

NOTA IMPORTANTE: L’articolo seguente fa riferimento alla versione 15.4.2. React.PropTypes è stato deprecato a partire da React v15.5, tutte le considerazioni fatte nell’articolo seguente sono valide, ma a partire dalla versione 15.5 di React, al posto di React.PropTypes è necessario usare la libreria prop-types.

Abbiamo visto diversi modi per creare un componente in React e abbiamo creato i nostri primi componenti. Come abbiamo detto nei precedenti articoli, i componenti sono i mattoncini essenziali per costruire le nostre applicazioni. L’obiettivo è quello di realizzare dei componenti riutilizzabili e configurabili. A questo scopo, React mette a disposizione l’oggetto props. Possiamo così passare diverse informazioni ai nostri componenti e se usiamo JSX possiamo farlo in maniera simile al linguaggio HTML.

L’oggetto Props

Riprendendo l’esempio dell’articolo precedente.

const rootNode = document.querySelector('.root');

const Hello = props => <h1>Ciao, {props.nome} {props.cognome}</h1>;

ReactDOM.render(<Hello nome="Mario" cognome="Bianchi" />, rootNode);

Abbiamo passato al componente Hello due props, la prima chiamata nome e la seconda cognome. All’interno della definizione del nostro componente, possiamo usare l’oggetto props. All’interno della sintassi JSX, adoperiamo delle parentesi graffe per indicare che vogliamo accedere al contenuto di una certa proprietà dell’oggetto props.

L’oggetto props non dovrà mai essere modificato all’interno del nostro componente. Le props, passate al nostro componente, devono essere considerate come delle costanti. Possono essere usate all’interno del componente come proprietà di sola lettura ma, non vanno mai modificate.

const rootNode = document.querySelector('.root');

const Hello = props => {
  /* possiamo assegnare il valore a una variabile e usare 
  *  poi tale variabile   
  */
  const nome = props.nome;
  const cognome = props.cognome;
  /*
  * NON MODIFICARE MAI Props!!!
  * props.nome = "Lisa"; NO! 
  * 
  */
  return <h1>Ciao, {nome} {cognome}</h1>;
}

ReactDOM.render(<Hello nome="Mario" cognome="Bianchi" />, rootNode);

L’oggetto props ha come unica responsabilità quella di passare delle informazioni a un componente che, a sua volta, potrà passare tutte o parte delle informazioni ricevute a un altro componente.

Vediamo un esempio in cui introdurremo altre piccole novità rispetto ai casi visti in precedenza.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>Esempio React</title>
</head>
<body>

  <div class="root"></div>

  <script src="https://unpkg.com/react@15.4.2/dist/react.js"></script>
  <script src="https://unpkg.com/react-dom@15.4.2/dist/react-dom.js"></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  
  <script type="text/babel">
    const rootNode = document.querySelector('.root');
    
    const dettagliScuderia = {
      nome: "Toro Rosso",
      piloti: ["Daniil Kvyat", "Carlos Sainz"]
    };
    /*
    *   NOTA: {piloti} è racchiuso tra parentesi graffe
    *         Stiamo usando la sintassi di assegnamento 
    *         di destrutturazione introdotta in ES6
    *         che rende possibile estrarre i valori da 
    *         oggetti e array e inserirli in variabili distinte.
    *
    *         È equivalente a  scrivere:
              
      const Piloti = (props) => {
        
        const piloti = props.piloti;
        
        const listaPiloti = piloti.map((pilota) => 
            <li key={pilota}>{pilota}</li>
        );
        // ecc...
      };
    *
    */
    
    const Piloti = ({piloti}) => {
      const listaPiloti = piloti.map((pilota) => 
        <li key={pilota}>{pilota}</li>
      );
      return (
        <ul>{listaPiloti}</ul>
      );
    };
    
    //  NOTA: {nome, piloti} sono racchiusi tra parentesi graffe
    const Scuderia = ({nome, piloti}) => (
      <div className="wrapper">
        <h1>{nome}</h1>
        // passa l'array piloti al componente Piloti
        <Piloti piloti={piloti} />
      </div>
    );


    ReactDOM.render(<Scuderia {...dettagliScuderia} />, rootNode);
  </script>
</body>
</html>

Per prima cosa, abbiamo definito una variabile dettagliScuderia contente le informazioni che passeremo ai nostri componenti. Abbiamo definito quindi due componenti Piloti e Scuderia tramite l’uso delle arrow function. In entrambi i casi abbiamo fatto uso della sintassi di assegnamento di destrutturazione che permette di estrarre i valori presenti nell’oggetto dettagliScuderia e di assegnarli direttamente a delle variabili le quali verranno utilizzate nel resto delle funzioni. In questo modo potremmo per esempio usare semplicemente la variabile piloti invece di dover scrivere props.piloti. Il componente Piloti riceve dal componente Scuderia l’array dei piloti attaverso l’oggetto props.

React Dev Tools dettagli applicazione di esempio sulla scuderia Toro Rosso

All’interno del componente Piloti usiamo la funzione Javascript Array.prototype.map(), passando come primo argomento una funzione. Per ogni elemento dell’array piloti, viene invocata la funzione che riceve come unico argomento l’elemento stesso dell’array e restituisce un React Element(<li key={pilota}>{pilota}</li>). Vedremo, nei prossimi articoli, perché abbiamo passato un attributo key. Per ora ci limitiamo a dire che serve per aiutare React a identificare quali elementi vengono aggiunti, cambiati o rimossi). Alla fine della funzione Array.prototype.map(), listaPiloti sarà un array di React Element.

Array listaPiloti contenente due React Elements

Alla fine, il componente Piloti restituisce un React Element contenente la lista dei piloti. Abbiamo usato la sintassi di interpolazione ({listaPiloti}) attraverso la quale chiediamo a JSX di mostrare il contenuto della variabile listaPiloti. JSX è abbastanza "intelligente" al punto che inserisce automaticamente i vari React Element contenuti nell’array come discendenti dell’elemento <ul></ul>

Alla funzione ReactDOM.render() abbiamo passato come primo argomento l’elemento React Scuderia. Abbiamo utilizzato l’operatore "spread" (…) ({…dettagliScuderia}) per passare l’intero oggetto. In questo modo React passerà al componente Scuderia due props distinte (props.nome e props.piloti).

<Scuderia {...dettagliScuderia} />

// è equivalente a

<Scuderia nome="Toro Rosso"  piloti=["Daniil Kvyat", "Carlos Sainz"]/>

Se avessimo passato dettagliScuderia come prop, Scuderia non avrebbe ricevuto nome e piloti come due props separate, ma l’intero oggetto dettagliScuderia. Nell’immagine sottostante è possibile notare la differenza fra i due casi.

Nel primo caso in cui passiamo l’intero oggetto come prop, otteniamo un risultato come quello mostrato nello screenshot qui in basso.

<Scuderia {dettagliScuderia} />
esempio di React Component Scuderia che riceve come prop un oggetto

Mentre nel nostro caso, Scuderia riceve nome e piloti come props.

<Scuderia {...dettagliScuderia} />
uso dell'operatore spread per passare delle informazioni al componente Scuderia

Default Props

React permette, inoltre, di utilizzare dei valori di default per le varie Props che un componente riceve. È possibile definire dei valori di default sia per i Functional Component che per i Class Component. Se un componente, per cui abbiamo definito dei valori di default per determinate props, non dovesse ricevere tali props, utilizzerà il valore di default.

const rootNode = document.querySelector('.root');

const Hello = ({nome}) => <h1>Hello, {nome}!</h1>;

Hello.propTypes = {
  nome: React.PropTypes.string
};

Hello.defaultProps = {
  nome: "Guest"
};

ReactDOM.render(<Hello />, rootNode); // Hello, Guest!

// ReactDOM.render(<Hello nome="Claudio" />, rootNode); // Hello, Claudio!

PropTypes predefinite e personalizzate

Nell’esempio appena visto, abbiamo definito un’oggetto propTypes, in cui ogni proprietà è il nome di una prop ricevuta dal componente e il valore della proprietà rappresenta il tipo della prop. In questo modo possiamo assicurare che un componente riceva un oggetto props le cui proprietà siano valide e significative. Potete far riferimento alla documentazione ufficiale per un elenco dettagliato dei vari tipi predefiniti che possono essere utilizzati.

const rootNode = document.querySelector('.root');

const Hello = ({nome}) => <h1>Hello, {nome}!</h1>;

Hello.propTypes = {
  nome: React.PropTypes.string
};

Hello.defaultProps = {
  nome: "Guest"
};

ReactDOM.render(<Hello nome={1} />, rootNode); // Error!!!

In questo caso, il componente Hello aspetta di ricevere una prop, chiamata nome, di tipo stringa. Avendo passato all’elemento <Hello /> un attributo nome di tipo numerico, React stamperà nella console un messaggio di errore per segnalarci che il tipo della proprietà nome non è valido.

screenshot che mostra un messaggio di errore del tipo Failed prop type

React fornisce un’ampia gamma di valori che ci permettono di validare le proprietà passate ai nostri componenti. Vediamo un altro esempio.

const rootNode = document.querySelector('.root');

const Greet = ({greeting, name}) => <h1>{greeting}, {name}!</h1>;

Greet.propTypes = {
    greeting: React.PropTypes.oneOf(['Buongiorno','Ciao']),
    name: React.PropTypes.string
};

Greet.defaultProps = {
    greeting: "Ciao",
    name: "Guest"
};

ReactDOM.render(<Greet  />, rootNode); // Ciao, Guest!
// ReactDOM.render(<Greet  name="Claudio" />, rootNode); // Ciao, Claudio!
// ReactDOM.render(<Greet  greeting="Buongiorno" name="Lisa" />, rootNode); // Buongiorno, Lisa!
// ReactDOM.render(<Greet  greeting="Buonanotte" name="Lisa" />, rootNode); // Error!!!

Questa volta, abbiamo stabilito che la proprietà greeting può assumere due soli valori e name deve essere una stringa.

Concatenando .isRequired al tipo di una proprietà, è possibile indicare che è obblligatorio passare una certa proprietà al componente.

const rootNode = document.querySelector('.root');

  const Greet = ({greeting, name}) => <h1>{greeting}, {name}!</h1>;

  Greet.propTypes = {
    // greeting è obbligatoria
    greeting: React.PropTypes.oneOf(['Buongiorno','Ciao']).isRequired,
    /*
    *   
    *   Un Custom Validator permette di definire una funzione personalizzata che sarà usata
    *   per validare una specifica prop, la prop 'name' in questo specifico caso.
    *
    *   La sintassi usata è equivalente a
    *
    *   name: function(props, propName, componentName) {...}
    *
    */
    name(props, propName, componentName) {
      /*
      *   props -> {name: "Claudio" greeting: "Ciao"}
      *   propName -> 'name'
      *   componentName -> Greet
      */
      if(/^[A-Z][a-z]+/.test(props[propName]) === false) {
        return new Error(
          'La prop "' + propName 
          + '" passata al componente ' + componentName
          + ' con valore ' + props[propName] + ' non è valida. "'
          + propName + '" deve essere una stringa di almeno'
          + ' due caratteri, con la prima lettera maiuscola.' 
        );
      }
    }
  };

ReactDOM.render(<Greet greeting="Ciao" name="Claudio" />, rootNode); // Ciao, Claudio!
// ReactDOM.render(<Greet greeting="Ciao" name="C" />, rootNode); // Error!!!

// ReactDOM.render(<Greet greeting="Ciao" name="H7-25" />, rootNode); // Error!!!

Nell’esempio appena visto, abbiamo indicato che la proprietà greeting può assumere due soli valori, ‘Buongiorno’ o ‘Ciao’, ed è obbligatoria. Per quanto riguarda la proprietà name, abbiamo definito un comportamento personalizzato tramite l’uso di una funzione che, come stabilito nella documentazione ufficiale, riceve tre parametri: l’oggetto props ricevuto dal componente, il nome della proprietà per cui si è definito un comportamento personalizzato e il nome del componente stesso. La funzione deve restituire un oggetto di tipo Error se il processo di validazione della proprietà fallisce. In questo caso, utilizziamo l’espressione regolare /[A-Z][a-z]+/ per verificare che la proprietà name sia una stringa con le seguenti proprietà:

  • Il primo carattere è una lettera maiuscola
  • Può contenere solo lettere
  • Deve essere una stringa di almeno due caratteri

Nel caso venga passato un valore non valido, riceveremo un messaggio di errore nella console con il testo da noi specificato.

Messaggio di Errore del tipo Failed prop type

Come sfruttare le potenzialità dell’oggetto Props

A volte può tornare utile usare una certa proprietà per costruirne altre in modo dinamico. Facciamo un esempio.

const rootNode = document.querySelector('.root');

const App = props => (
  <div>
    <Button color="indigo" />
    <Button color="cyan" />
    <Button color="light-blue" />
  </div>
)

// Es. <button className="btn btn-indigo">click</button>
const Button = props => <button className={`btn btn--${props.color}`} >click</button>;

Button.propTypes = {
  color: React.PropTypes.oneOf(['indigo', 'cyan', 'light-blue']).isRequired
};

ReactDOM.render(<App />, rootNode);

E questo è il risultato.

Screenshot con tre pulsanti colorati

Abbiamo creato in maniera dinamica il nome della classe che viene passata all’elemento <button /> usando il valore della proprietà color dell’oggetto props ricevuto dal componente Button. Ricordiamo che al contrario del linguaggio HTML, in JSX usiamo l’attributo className per indicare eventuali classi dell’elemento. Il motivo è che la keyword class è riservata in Javascript.

Nel prossimo articolo vedremo come rendere i nostri componenti più dinamici grazie all’introduzione dell’oggetto state, disponibile solo per i Class Component.

Pubblicità
Articolo precedente
Articolo successivo