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.
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.
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.
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.
Ma se <Menu></Menu> ha un solo discendente, cosa succede?
<Menu>
<a href="/">Home</a>
</Menu>
Nella console riceveremo un messaggio di errore!
Il motivo dellโerrore รจ che nel primo caso avevamo un array di oggetti.
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.
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.
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.