Passa al contenuto principale

Mood Board

Anteprima desktop del progetto React Mood Board: visualizzazione di un layout a griglia con card dinamiche che mostrano immagini, colori dominanti e titoli descrittivi

Il Progetto

Build a Mood Board è un Lab freeCodeCamp dove ho applicato Props, .map() e Spread Syntax per creare una griglia di card visive. Tecnicamente semplice, ma mi ha fatto ragionare su vincoli architetturali nascosti e dipendenze implicite che possono generare bug "silenziosi".

Codice Sorgente

/* DESIGN
------
* This file contains the React logic for the Mood Board application
* The architecture follows this component-based flow:
*
* Data configuration (the content):
* - Instead of writing repetitive HTML, I defined a `moodData` constant array inside the main component
* - This array holds all the specific details (colors, images, descriptions) in one place, acting
* like a local database for the view
*
* The Parent component (<MoodBoard>):
* - Acts as the layout orchestrator
* - It renders the main title (<h1>) outside the grid to span the full width
* - It contains a wrapper <div> ".mood-board" which applies the CSS Grid layout provided by freeCodeCamp
* - Inside this wrapper, it executes the logic (.map) to transform the raw data into visible UI cards
*
* The Child component (<MoodBoardItem>):
* - Serves as a reusable template (the "Card")
* - It is "dumb" (stateless): it simply receives style and content via props and renders them into
* the specific HTML structure required by the CSS (.mood-board-item, .mood-board-image,
* and .mood-board-text)
*/

/* freeCodeCamp instructions:
* 1. You should export a MoodBoardItem component. ✔️
* 2. Your MoodBoardItem component should return a div with a class of
* mood-board-item at its top-level element. ✔️
* 3. The background color of the .mood-board-item element should be set
* to the value of color prop using inline styles. ✔️
* 4. Your MoodBoardItem component should render an img element with a class
* of mood-board-image and its src set to the value of the image prop. ✔️
* 5. Your MoodBoardItem component should render an h3 element with a class
* of mood-board-text and its text set to the value of the description prop. ✔️
* 6. You should export a MoodBoard component. ✔️
* 7. Your MoodBoard component should return a div as its top-level element. ✔️
* 8. Your MoodBoard component should render an h1 element with a class of
* mood-board-heading and the text Destination Mood Board. ✔️
* 9. Your MoodBoard component should render at least three MoodBoardItem components,
* each should pass color, image, and description props with valid values. ✔️
* 10. Your MoodBoard component should be rendered to the page's #root element. ✔️
*/

export function MoodBoardItem({ color, image, description }) {
/*
* Because that JSX `style` isn't like HTML strings. It requires a JavaScript Object
* The syntax `style={{ key: value }}` uses the first braces for the JS portal
* and the second for the Object literal.
*
* React properties must be CamelCase: `background-color` becomes `backgroundColor`
*/
return (
<div className="mood-board-item"
style={{ backgroundColor: color }}
>
<img src={image} alt={description} className="mood-board-image" />
<h3 className="mood-board-text">
{description}
</h3>
</div>
)
}

export function MoodBoard() {
const moodData = [
{
id: 1,
image: "https://cdn.freecodecamp.org/curriculum/labs/pathway.jpg",
description: "Hidden Lagoon",
color: "#2E4A3D", // I extracted dominant colors from images to ensure high contrast with white text
},
{
id: 2,
image: "https://cdn.freecodecamp.org/curriculum/labs/shore.jpg",
description: "Crystal Shores",
color: "#005B7F",
},
{
id: 3,
image: "https://cdn.freecodecamp.org/curriculum/labs/grass.jpg",
description: "Blooming Horizon",
color: "#8C3A3A",
},
{
id: 4,
image: "https://cdn.freecodecamp.org/curriculum/labs/ship.jpg",
description: "Crimson Voyage",
color: "#A64B2A",
},
{
id: 5,
image: "https://cdn.freecodecamp.org/curriculum/labs/santorini.jpg",
description: "Aegean White",
color: "#004E92",
},
{
id: 6,
image: "https://cdn.freecodecamp.org/curriculum/labs/pigeon.jpg",
description: "Urban Observer",
color: "#6B5B4A",
}
];

return (
<div>
<h1 className="mood-board-heading">Destination Mood Board</h1>

<div className="mood-board">
{/*
* The transformation (.map):
* I discovered that React renders Arrays of Components automatically.
* Unlike `forEach` (which does work but returns nothing), `.map()` returns
* a NEW array (immutability concept) where every data object is transformed
* into a JSX Component
*
* The spread syntax shortcut ({...item}):
* Instead of writing: `color={item.color} image={item.image} description={item.description}`
* I use `{...item}` which opens the object properties directly into props, to avoid
* repetitive code
*
* Important constraint: I realized that this shortcut relies on a strict naming contract
* because React acts like a "Blind Courier": it simply takes every key from the data object
* and delivers it as a prop, without translating or checking if the component understands it
* For example, if my data used the key "imgUrl" but the component was waiting for "image",
* the delivery would technically succeed, but the component would receive a prop it doesn't
* recognize (imgUrl), leaving the actual "image" prop undefined
* For this to work, the Data Model and the Component Interface must speak exactly the same language.
*/}
{moodData.map((item) => (
<MoodBoardItem
key={item.id}
{...item}
/>
))}
</div>
</div>
);
}

Come al solito mi sono "sfogato" nei commenti, ma ci tengo a riportare alcuni passaggi anche qui.

Il Ripasso su UX Engineer Log

Di recente sono stato a Roma per partecipare a due laboratori universitari di design. Durante il viaggio, ho deciso di sostituire la lettura dei soliti libri con il mio UX Engineer Log. Era da tempo che volevo ripassare tutto ciò che ho appreso in questi mesi, sfruttando questo strumento per il suo vero scopo: fungere da Secondo Cervello. Sebbene ne avessi già tratto beneficio consultandolo durante i progetti, non l'avevo mai usato sistematicamente per lo studio attivo.

Ho iniziato sfogliando i singoli progetti, ma ho capito subito che l'approccio era dispersivo. La strada giusta erano i Vademecum. Sono partito dall'HTML, ma l'ho abbandonato quasi subito: sentivo di possedere già molto bene quei concetti. Ho deciso quindi di alzare l'asticella passando a JavaScript. E lì sono stato "rapito". Mi sono immerso nella prima sezione, densissima, cercando di leggerla con gli occhi di un utente esterno. Per quanto il mio giudizio sia di parte (ho un inevitabile conflitto di interessi), ne sono rimasto sorpreso. Il metodo con cui ho aggregato le informazioni progetto dopo progetto, appuntandomi le spiegazioni del Code Tutor in AppFlowly nel blocco "Add al Vademecum", ha creato una progressione naturale. Rileggere tutto insieme è stato soddisfacente: non erano nozioni astratte, ma concetti assimilati attraverso la pratica, finalmente ordinati. Adoro questo modo di documentare e trovo estremamente gratificante farlo.
Credevo che le basi di JavaScript fossero ormai assodate, ma mi sbagliavo. Il ripasso ha fatto emergere dubbi che ho prontamente chiarito e trasformato in nuove note: le integrerò presto nel Vademecum per renderlo ancora più cristallino per chi leggerà in futuro.

Architettura Data-Driven

L'adozione di .map() e dello Spread Operator è scaturita quasi naturalmente dal ripasso appena concluso. Sebbene siano pattern che avrei probabilmente utilizzato comunque, averli freschi nella memoria ha trasformato una possibilità in una scelta consapevole e immediata (il bias di disponibilità ha giocato il suo ruolo). Anziché cedere all'hardcoding (scrivere manualmente sei componenti <MoodBoardItem />), ho optato per un approccio Data-Driven orientato alla scalabilità:

  • Array moodData: Funge da database locale. Si occupa esclusivamente dei dati puri (id, immagini, descrizioni e colori dominanti per il contrasto). Aggiungere una card significa solo aggiungere un oggetto qui, senza toccare il resto del JSX.
  • Componente <MoodBoard />: È l'orchestratore. Non conosce il contenuto specifico, ma sa come trasformare una lista di dati in elementi UI tramite .map(), generando la griglia dinamicamente.
  • Componente <MoodBoardItem />: È il template visivo. Riceve i dati e li "impacchetta" nella struttura HTML richiesta dal CSS, senza preoccuparsi della logica.

Perché .map() e non forEach

La scelta di .map() è dettata dalla natura dichiarativa di React. In JSX, ogni espressione deve restituire un valore renderizzabile, cosa che forEach non fa (esegue un ciclo ma restituisce undefined in termini di UI). Al contrario, .map() produce un nuovo array (garantendo immutabilità dell'array originale): prende un array di dati in entrata e restituisce un nuovo array di componenti in uscita. React renderizza automaticamente gli array di componenti.

Questa distinzione è fondamentale per la Reconciliation. Dato che React decide se aggiornare la UI confrontando i riferimenti in memoria, se modificassi l'array originale (es. con push), il riferimento rimarrebbe identico e React ignorerebbe il cambiamento. .map(), restituendo sempre un nuovo array, garantisce un nuovo riferimento, segnalando a React che è tempo di aggiornare la UI.

Lo Spread Operator e il "Blind Courier"

Ho utilizzato lo Spread Operator ({...item}) per pulizia e comodità, ma questa scelta mi ha fatto riflettere su come React agisca da "Corriere Cieco" (Blind Courier).

La libreria si preoccupa solo dell'etichetta di spedizione (key) per tracciare il componente nel Virtual DOM; tutto il resto del contenuto (...item) viene consegnato "a scatola chiusa". Qui nasce un vincolo critico: React non controlla se il destinatario capisce il contenuto. Se l'array contiene la chiave imgUrl ma il componente si aspetta image, la consegna avviene con successo, ma il componente riceverà una prop (proprietà) sconosciuta, lasciando quella corretta undefined. Per usare questa scorciatoia, Data e Componente devono parlare esattamente la stessa lingua: è una dipendenza implicita che, se avessi ignorato, avrei generato bug silenziosi.

Rileggendo il codice del progetto Footer, ho notato di aver utilizzato la sintassi {/* commento */} anche all'esterno dei componenti. Ho scoperto che si trattava di un errore concettuale: le parentesi graffe servono ad aprire il "portale" JavaScript dentro il JSX; fuori da esso, siamo in un JavaScript standard e bastava un classico /* commento */.

Ho deciso deliberatamente di non correggere quel file. Anche se potrebbe sembrare un autogol agli occhi di un recruiter, questo percorso l'ho fondato sulla cronologia reale dei log. Preferisco mostrare la trasparenza della mia evoluzione piuttosto che una perfezione retroattiva costruita a posteriori.

Vincoli CSS Preconfezionati

L'analisi del CSS fornito da freeCodeCamp ha definito i confini entro cui il mio React doveva muoversi:

  • Body: Il background giallo pallido (#ffffcc) ha dettato la scelta di colori scuri e saturi per l'array moodData, garantendo il contrasto necessario col testo bianco.
  • Grid Hardcoded: La regola grid-template-columns: repeat(3, 1fr) forza un layout a tre colonne. Questa è stata una scoperta critica: ho dovuto posizionare l'<h1> fuori dal container .mood-board. Se l'avessi messo dentro, sarebbe stato trattato come un elemento della griglia e schiacciato in una colonna, rompendo il design. In poche parole il CSS ha imposto la struttura, React l'ha riempita.
  • Card Anatomy: Con un'altezza fissa di 250px e Flexbox gestito dal CSS, il componente <MoodBoardItem /> è diventato puramente un riempitivo di contenuto, delegando interamente il layout al foglio di stile.

Cosa Ho Imparato

Inline Styles (Il Portale JavaScript):

  • Ripassato che l'attributo style in JSX non accetta stringhe come in HTML, ma esige oggetti JavaScript. La sintassi {{ }} non è doppia graffa a caso: la prima apre il "portale JavaScript", la seconda definisce l'oggetto letterale.
  • Cambio di mentalità obbligato: da CSS standard (background-color) a proprietà DOM JavaScript (backgroundColor). Non stiamo scrivendo fogli di stile, stiamo programmando lo stile.

.map() come Catena di Montaggio:

  • Abbandonato l'approccio imperativo (cicli for ad esempio) per quello dichiarativo. .map() agisce come una catena di montaggio che prende materie prime (dati grezzi) e sforna prodotti finiti (Componenti UI) in un colpo solo.
  • Cruciale per React: restituendo un nuovo array (invece di modificare l'esistente), garantisce l'immutabilità necessaria affinché il framework rilevi i cambiamenti e aggiorni la vista.

Spread Syntax e Contratti Impliciti:

  • L'uso di {...item} è una scorciatoia elegante ma pericolosa: crea una dipendenza invisibile tra il Data Model e l'interfaccia del Componente.
  • React agisce come un "Corriere Cieco": consegna tutte le chiavi del database direttamente al componente. Se i nomi non coincidono perfettamente (es. imgUrl vs image), il dato si perde silenziosamente nel tragitto.

Gerarchia: Il CSS comanda, React esegue:

  • Scoperto che i vincoli del layout (CSS Grid) possono dettare l'architettura dei componenti. L'<h1> è stato spostato fuori dal container .mood-board non per logica React, ma per non essere schiacciato dalle regole della griglia.
  • La lezione: React riempie le scatole, ma spesso è il CSS a decidere dove quelle scatole possono stare. Non si può ignorare il "contratto visivo" quando si progetta l'albero dei componenti.

Grammatica dei Commenti (Context Switching):

  • Appreso che JSX non è HTML nemmeno nei commenti. All'interno del rendering visivo, i commenti devono essere incapsulati in {/* ... */} per essere interpretati come JavaScript e non renderizzati come testo.
  • È un continuo cambio di contesto: fuori dal return siamo in JavaScript puro (// o /* */), dentro siamo nel Virtual DOM che richiede le graffe di escape.

Vademecum Git & GitHub

Attualmente ho in cantiere un nuovo Vademecum dedicato a Git e GitHub, mantenendo lo stesso approccio "Real World" che ho già applicato a quelli su HTML, CSS, JavaScript e UX & UI. È un progetto in divenire, nato per consolidare i concetti appresi durante lo sviluppo con Docusaurus. Non mi sono però limitato alla mia esperienza "in solitaria": ho voluto approfondire le dinamiche reali del lavoro in team, studiando i flussi di lavoro collaborativi standard. Ho arricchito il tutto con una raccolta essenziale dei comandi da terminale che si sono rivelati, nel pratico, tra i miei migliori alleati quotidiani.


Next:
Consolidare le basi tramite Quiz e imparare a lavorare con State, Hooks ed Eventi, per poi applicare tutto nella Toggle Text App.