back to top

Componenti React annidati

In React รจ possibile annidare i componenti. Allโ€™interno del metodo render() di un componente possiamo usare uno o piรน componenti predefiniti o creati da noi. รˆ possibile annidare Functional Component allโ€™interno di Class Component e viceversa.

Riprendiamo un esempio giร  visto nei precedenti articoli.

Pubblicitร 
const Button = props => <button className={`btn btn--${props.color}`} >click</button>;

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

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

Il Functional component App restituisce un React Element div che ha come diretti discendenti tre elementi <Button /> costruiti sulla base del Functional Component Button da noi definito. Per ora ci siamo limitati a copiare e incollare lo stesso elemento piรน volte, cambiando il valore della proprietร  โ€˜colorโ€™ anche se non รจ una buona pratica.

Lista di componenti dello stesso tipo

Una possibile alternativa รจ quella di utilizzare un array di valori e la funzione Array.prototype.map() per costruire la struttura del nostro componente in maniera dinamica. Riscriviamo lโ€™esempio di sopra.

const colors = ['indigo', 'cyan', 'light-blue'];

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

const App = ({colors}) => {
  // buttons รจ un array di Elementi React <Button />
  const buttons = colors.map(color => <Button color={color} />);
  return (
    <div>
      {buttons}
    </div>
  )
}

ReactDOM.render(<App colors={colors} />, rootNode);

Applichiamo la funzione Array.prototype.map() allโ€™array colors. Ad ogni iterazione Array.prototype.map() riceve come primo parametro un elemento dellโ€™array colors e restituisce un React Element costruito utilizzando il componente Button da noi definito. Al termine della funzione map(), la variabile buttons sarร  un array di Elementi React. Tramite interpolazione, inseriamo i pulsanti allโ€™interno di un elemento div. Se aprite la console degli strumenti per sviluppatori del browser, noterete perรฒ un errore.

Messaggio di errore unique key

React ci avvisa che ogni elemento del nostro array deve avere una prop "key" di tipo stringa che sia unica fra gli altri elementi del medesimo array. Possiamo comunque riusare la stessa prop "key" allโ€™interno di due array differenti. Correggiamo lโ€™esempio appena visto. Nel nostro caso possiamo passare uno dei colori come prop "key".

const colors = ['indigo', 'cyan', 'light-blue'];

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

const App = ({colors}) => {
  // buttons รจ un array di Elementi React Button
  const buttons = colors.map(color => <Button key={color} color={color} />);

  return (
    <div>
      {buttons}
    </div>
  )
}

ReactDOM.render(<App colors={colors} />, rootNode);

React utilizza la proprietร  "key" per identificare quale elemento rimuovere, aggiornare o aggiungere allโ€™interno del VirtualDOM. Proprio per questo motivo bisogna aggiungere una proprietร  "key" agli elementi allโ€™interno dellโ€™array che abbiamo costruito.

Comporre i Componenti tramite this.props.children

Finora abbiamo usato JSX per creare degli elementi e abbiamo utilizzato dei tag del tipo <Button /> con la coppia di caratteri "/>" a chiudere il tag. Babel tradurrร  questo tag in una chiamata alla funzione React.createElement(Button, null). Il React Element costruito sulla base del componente "Button" non presenta degli elementi discendenti. A volte puรฒ perรฒ essere utile realizzare strutture piรน complesse e annidare i React Element cosรฌ come siamo soliti fare con gli elementi HTML.

Consideriamo lโ€™esempio di un componente che chiameremo Card e consideriamo il seguente codice scritto utilizzando la sintassi JSX. Creiamo quindi un React Element <Card></Card> a partire dal componente Card. Questo nuovo elemento React avrร  fra i suoi discendenti tre React Element, due dei quali (Header, Footer) saranno creati sulla base dei componenti da noi definiti.

<Card>
  {/* 
      L'elemento Card avrร  degli Elementi annidati. Due di questi elementi React saranno
      costruiti sulla base di Componenti React da noi definiti.
  */}
  <Header text="Header 1"/>
  <p>Nullam quis risus eget urna mollis ornare vel eu leo.</p>
  <Footer text="Contenuto del Footer"/>
</Card>

Allโ€™interno del componente Card possiamo accedere agli elementi annidati attraverso lโ€™uso di props.children. Creiamo innanzitutto il componente Card.

const Card = props => <div>{props.children}</div>;

Creiamo ora i due componenti Header e Footer.

const Header = props => <h1>{props.text}</h1>;

const Footer = props => <p>{props.text}</p>;

E infine creiamo un componente App che useremo poi nella funzione ReactDOM.render().

const App = props => (
  <Card>
    {/* 
        L'elemento Card avrร  degli Elementi annidati. Due di questi elementi React saranno
        costruiti sulla base di Componenti React da noi definiti.
    */}
    <Header text="Header 1"/>
    <p>Nullam quis risus eget urna mollis ornare vel eu leo.</p>
    <Footer text="Contenuto del Footer"/>
  </Card>
)

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

Allโ€™interno del componente Card, possiamo chiamare console.dir(props.children) e quella che segue e unโ€™immagine di ciรฒ che viene mostrato nella console di Chrome.

Rappresentazione dell'oggetto props.children

Children Utilities

Abbiamo visto come utilizzare props.children allโ€™interno dei nostri componenti. Consideriamo un altro esempio. Creiamo un componente Menu allโ€™interno del quale utilizzeremo props.children per accedere ai link (elementi <a href="">Nome link</a>.) che passeremo come discendenti dellโ€™Elemento React <Menu></Menu>.

const Menu = props => {
  /*     
  *     child.props.children corrisponde al testo di ciascun link
  *     per cui otterremo "Home" per il primo link, Contatti per il secondo ecc...
  */
  const listItems = props.children.map(child => <li key={child.props.children}>{child}</li>);
  return (
    <ul>
      {listItems}
    </ul>
  )
}  

const App = props => (
  <Menu>
    <a href="/">Home</a>
    <a href="/contatti/">Contatti</a>
    <a href="/servizi/">Servizi</a>
  </Menu>
)

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

Fin qui tutto funziona perfettamente, allโ€™interno della nostra pagina vedremo una lista di link.

Codice JSX del componente Menu

Ma se <Menu></Menu> ha un solo discendente, cosa succede?

<Menu>
  <a href="/">Home</a>
</Menu>

Nella console riceveremo un messaggio di errore!

messaggio TypeError dovuto al fatto che props.children non รจ un array

Il motivo dellโ€™errore รจ che nel primo caso avevamo un array di oggetti.

output nella console dell'array di oggetti this.props.children

Nel secondo caso abbiamo un singolo oggetto e, quindi, non รจ possibile invocare la funzione Array.prototype.map() visto che in questo caso props.children non รจ un Array.

output nella console dell'oggetto this.props.children

Proprio per risolvere questo tipo di situazioni, ci viene in aiuto lโ€™API di React. Al posto di props.children possiamo utilizzare React.Children che ci fornisce una serie di metodi per lavorare piรน facilmente con props.children. Uno dei metodi che possiamo usare per risolvere lโ€™errore dellโ€™esempio visto sopra รจ React.Children.map(children, fn).

const Menu = props => {

  const listItems = React.Children.map(
    props.children, 
    child => <li key={child.props.childre>{child}</li>
  );
  
  return (
    <ul>
      {listItems}
    </ul>
  )
}  

const App = props => (
  <Menu>
      <a href="/">Home</a>
  </Menu>
)

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

In questo caso anche con un solo link, non avremo nessun problema. React.Children fornisce altri metodi interessanti come forEach(), count() e toArray(). Per maggiori dettagli potete consultare la documentazione ufficiale di React.

Lโ€™ultima interessante funzione che vedremo in questo articolo รจ React.cloneElement(element, [props], [โ€ฆchildren]) che clona e restituisce un nuovo elemento React utilizzando come punto di partenza lโ€™elemento passato come primo argomento. Le props, passate come secondo argomento, saranno combinate con le proprietร  dellโ€™elemento originale. Se passiamo il terzo argomento opzionale children, questo sostituirร  gli eventuali elementi figli dellโ€™elemento di partenza.

Questa funzione puรฒ essere particolarmente utile qualora, allโ€™interno di un componente, si vogliano passare delle props agli elementi contenuti in props.children.

Riprendiamo lโ€™esempio visto sopra e modifichiamolo usando React.cloneElement().

const ListItem = props => <li>{props.children}</li>;

const Menu = props => {

  const func = child => React.cloneElement(<ListItem>{child}</ListItem>, {
    key: child.props.children,
    otherProp: "Prop passata da Menu"
  });

  const listItems = React.Children.map(props.children, func);

  return (
    <ul>
      {listItems}
    </ul>
  )
}  

const App = props => (
  <Menu>
    <a href="/">Home</a>
    <a href="/contatti/">Contatti</a>
    <a href="/servizi/">Servizi</a>
  </Menu>
)

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

Definiamo tre componenti: App, Menu e ListItem. Allโ€™interno del componente Menu avremo accesso a props.children che sarร  un array di elementi React, corrispondenti ai link discendenti dellโ€™elemento <Menu> </Menu>. Allโ€™interno del componente Menu, usiamo la funzione React.Children.map() e passiamo props.children come primo argomento. Il secondo argomento sarร  una funzione che riceverร , uno alla volta, gli elementi dellโ€™array props.children. Ogni elemento sarร  inserito come discendente di <ListItem>, passato poi come primo argomento della funzione React.cloneElement(). Il risultato di ogni chiamata alla funzione func() sarร  un nuovo elemento <ListItem /> con attributi key e otherProp che saranno accessibili, allโ€™interno del componente ListItem attraverso lโ€™oggetto props.

screenshot di React Dev Tools in cui viene mostrata la struttura JSX dell'elemento Menu

Se volete maggiori informazioni, trovate tutti i dettagli necessari nella documentazione ufficiale (React.cloneElement()).

Nel prossimo articolo parleremo del ciclo di vita di un componente React.

Pubblicitร