Full stack app per inviare feedback

Questo progretto è una della challenges di Frontend Mentor. Utilizzo di frequente quest’ultimo per sviluppare o solidificare le mie conoscenze nell’ambito dello sviluppo web senza la necessità di dar vita ad un’idea e design da zero.

Frontend Mentor quindi fornisce delle immagini delle varie pagine, assets, e azioni che il progretto dovrebbe avere per poi lasciare la libera scelta per quanto riguarda tutto il resto degli strumenti da utilizzare.

Overview

La pagina iniziale è caratterizzata da una lista di tutti i suggerimenti e bug report per la nostra ipotetica app principale i quali possono essere upvotati, filtrati e riordinati.

Nella modalità desktop dispone di una barra laterale la quale si collassa per posizionarsi al di sopra del contenuto principale nella modalità mobile per poi poter essere aperta con un click. Questa comprende dai tasti grazie ai quali è possibile filtrare i contentuti in base alla categoria di feedback e il conteggio dinamico della features, divise in pianificate, in progresso e live, e un link tramite il quale si può passare alla pagina dedicata a quest’ultime.

Nella pagina di ciascun feedback è possibile visualizzare i commenti, inviarne di nuovi o rispondere a commenti già inviati.

È anche possibile aggiornare il feedback inviato tramite il l’apposito pulsante che ci porta ad una pagina dedicata. L’aggiornamento dei dati avviene solo nel caso in cui l’app abbia verificato la validità dei dati sia Server-side che Client-side.

La pagina della Roadmap mostra i feedback nelle tre categorie elencate in precendenza sovrapposte l’una sull’altra della stessa categoria.

Frontend

Mi piace tenermi informato degli sviluppi nel campo delle tecnologie web per non essere colto alla sprovvista quando altri framework, magari più popolari, decidono di prendere spunto da queste idea e aggiungerle al loro repertorio.

Detto questo, ho deciso di sperimentare con SolidJS, dopo averne sentito parlare molto bene, per capire quali fossero i suoi vantaggi e le differenze rispetto a ReactJS quando all’apparenza sembrano molto simili.

// React
function Counter() {
  const [count, setCount] = useState(1);
  const increment = () => setCount(count => count + 1);

  return (
    <button type="button" onClick={increment}>
      {count}
    </button>
  )
}
// Solid
function Counter() {
  const [count, setCount] = createSignal(1);
  const increment = () => setCount(count() + 1);

  return (
    <button type="button" onClick={increment}>
      {count()}
    </button>
  )
}

React e Solid sono in realtà molto diversi nel modo in cui lavorano, la più grande differenza tra i due sta nel fatto che i componenti di Solid sono delle setup functions, vengono, quindi, renderizzati soltanto una volta, non ogni volta che lo stato cambia come in React.

Solid utilizza signals per rendere reattivi i valori e quando questi si aggiornano viene re-renderizzata solo la piccola parte in cui vengono chiamati, non tutto il componente.

I Signals sono degli event emitters che tracciano una lista dei loro subscriber e quando il loro valore cambia li notificano.

Backend

Una delle idee aggiuntive che Frontend Mentor proponeva era lo sviluppare l’app Full Stack. Shuttle-rs, che mi permette di hostare il server gratis, ottimo per una demo, e Axum un framework web scritto in Rust, sono la combinazione perfetta per questo.

Adoro utilizzare Rust ogni volta che ne ho la possibilità, trovo che i messaggi di errore e le restrizioni che il compiler impone mi aiutino ad usare dei paradigmi di sviluppo diligenti, che posso poi applicare, non solo a Rust stesso ma, anche in altri linguaggi di programmazione.

Ci sono moltissime cose che amo di Rust, iterators, l’enum Option<T>, the borrow checker, e altri ancora, ma la cosa che mi manca più di ogni altra quando torno nel mondo di Typescript è Result<T, E>, questo enum ti comunica la presenza di un potenziale errore e ti impone di affrontare il problema.

In Javascript/Typescript poi usare throw per ritornare immediatamente un errore da una funzione ma:

  • Il tipo di ritorno della funzione non ti dice che può esserci un errore, non sai che ci sia finchè non controlli personalmente implementazione della funzione. Ci sono delle issue aperte nella repo di Typescript che spiegano il problema, come questa del 2016, ma non è ancora una caratteristica del linguaggio.

  • Non sei obbligato ad usare un try {} catch {} per affrontare l’errore subito, puoi non farlo e lasciarlo fare alla funzione che ti ha chiamato, e quella può non farlo e così via.

Questo significa che l'errore verrà scoperto, se verrà scoperto, mentre il programma è già avviato, magari in produzione, invece di scoprirlo mentre il programma viene compilato.

Result è un semplice enum, ha due valori: Ok(T) dove T è il tipo del valore corretto, e Err(E) dove E è il tipo dell’errore.

Puoi usare match per distinguere tra le due possibilità e trattarle in modo diverso.

fn read_username_from_file() {
  let file_result = File::open("hello.txt"); // Result<File, io::Error>

  let file = match rile_result {
    Ok(file) => file,
    Err(error) => panic!("Problema ad aprire il file, {:?}", error),
  };
}

Molte volte però è lecito lasciar decidere alla funzione chiamante in che modo proseguire, per questo Rust ha l’operatore ?, il quale fa un match dietro le quinte e, se trova la variante Ok(T) assegna alla variabile il tipo T, altrimenti ritorna immediatamente dalla funzione con l’errore ricevuto.

fn read_username_from_file() -> Result<String, io::Error> {
  let file = File::open("hello.txt")?;

  let mut username = String::new();
  username = file.read_to_string(&mut username)?;

  Ok(username) // linee senza ; ritornano implicitamente