React

Come costruire una pagina impostazioni che regge la crescita

Una pagina impostazioni regge la crescita se le sezioni sono dati, non JSX copiato. Ecco struttura, salvataggio per sezione e il limite dei blocchi pronti.

In sintesi

  • Una sezione, una route e un salvataggio: non dodici tab in una pagina sola.
  • Le impostazioni sono dati: aggiungere un'opzione deve costare un oggetto.
  • I blocchi del catalogo dichiarano «see source»: verifica la licenza prima di spedire.

Una pagina impostazioni che regge la crescita è un elenco di sezioni dichiarate come dati, non una sequenza di componenti scritti a mano. Aggiungere un'opzione deve costare un oggetto, non una copia di JSX.

Il punto in cui si rompe è prevedibile: la terza sezione. Arriva il primo salvataggio per sezione, poi il primo campo che dipende da un altro, poi il primo permesso negato.

Una sezione, un salvataggio

  • Una route per sezione: /settings/notifications deve esistere come link.
  • Stato locale al form e salvataggio esplicito, non un autosave invisibile.
  • Conferma ed errore vicini al pulsante che li ha prodotti.
  • Campi disabilitati spiegati, non solo spenti.
  • Permessi letti dal server: il client non decide chi può cambiare cosa.

Il blocco settings di 1st-pouf mostra sezioni a tab con react-hook-form; openlmis-settings-list è la variante a righe con badge e valori in sola lettura. Sfoglia la categoria settings. Sorgenti: github.com/moji2002/1st-pouf e github.com/SolDevelo/soldevelo-registry, licenza dichiarata a monte «see source».

La categoria stretta non è una lacuna del catalogo: dice che «impostazioni» non è un tipo di pagina. Il blocco di 1st-pouf non dichiara categorie e si trova per titolo, non per filtro. Cerca per nome, apri il codice e leggi come tratta i campi.

Dati, non colonne

npx shadcn@latest add tabs switch label
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";

type Setting = {
  id: string;
  label: string;
  description: string;
  defaultOn: boolean;
};

export function SettingsSection({ title, settings }: { title: string; settings: Setting[] }) {
  return (
    
      {title}
      {settings.map((setting) => (
        
          
            {setting.label}
            {setting.description}
          
          
        
      ))}
    
  );
}

L'esempio concreto: la sezione è un oggetto

Tre file bastano. Un file di configurazione dichiara le sezioni, una route dinamica legge lo slug e il componente disegna le righe. Aggiungere «avvisi di sicurezza» costa una voce nell'array e una riga nella tabella dei valori; nessuna pagina viene copiata.

export type Field = {
  id: string;
  label: string;
  help: string;
  kind: "switch" | "select";
  options?: string[];
  dependsOn?: { field: string; equals: boolean };
  permission: "member" | "admin";
};

export const sections = {
  notifications: {
    title: "Notifiche",
    route: "/settings/notifications",
    fields: [
      { id: "digest", label: "Riepilogo settimanale", help: "Una email il lunedì.", kind: "switch", permission: "member" },
      { id: "mentions", label: "Menzioni", help: "Solo quando qualcuno ti nomina.", kind: "switch", dependsOn: { field: "digest", equals: true }, permission: "member" },
      { id: "channel", label: "Canale", help: "Dove arriva il riepilogo.", kind: "select", options: ["email", "slack"], permission: "admin" },
    ],
  },
} satisfies Record;

Il tipo Field è il contratto. Chi aggiunge un campo non tocca il JSX: sceglie kind, scrive help e dichiara permission. La dipendenza dependsOn spegne «Menzioni» finché il riepilogo è spento, e la ragione sta nel dato, non in un ramo nascosto.

Quando la pagina cresce

Con lo schema, una nuova impostazione è una voce nell'array e un campo nel database. Permessi, audit log e valori ereditati restano fuori dal componente: li decide il server. Se un campo dipende da un altro, la dipendenza sta nello schema, non in un if dentro il JSX.

Limite: entrambi i blocchi dichiarano «see source» come licenza, e settings di 1st-pouf porta quattordici registryDependencies. Non è un componente da copiare in cinque minuti: leggilo come riferimento di struttura e verifica la licenza prima di spedire.

La regola di decisione

Regola: un campo che richiede un permesso diverso dagli altri, o che dipende da un altro campo, prende una sezione con la sua route. Tutto il resto resta una riga nella lista. La soglia evita i due estremi: dodici tab in una pagina sola e una pagina per un singolo interruttore.

L'errore da evitare

L'errore da evitare è fidarsi di defaultChecked. Lo switch mostra il nuovo valore prima della risposta, e se il salvataggio fallisce nessuno lo riporta indietro: la pagina dichiara «attivo» un valore che il database non ha mai ricevuto. Tieni il valore nello stato, disegna lo switch da lì e ripristina lo stato precedente quando la risposta è un errore.

Per scegliere il blocco giusto leggi Migliori settings blocks 2026 e la checklist in 12 punti per componenti React accessibili. Se devi decidere se partire da un tema pronto, il confronto Temi shadcn gratis o premium mette i costi sul tavolo.

Domande frequenti

Una pagina sola o una route per sezione?

Una route per sezione, perché il link diretto è la funzione principale di una pagina impostazioni. Una pagina con dodici tab non si può linkare né ricaricare senza perdere il punto.

Autosave o pulsante Salva?

Pulsante Salva per sezione, salvo impostazioni banali come un toggle. L'autosave invisibile rende impossibile capire cosa è stato scritto e quando.

Come gestisco i campi disabilitati?

Con una spiegazione accanto al campo e un motivo leggibile, non solo un attributo disabled. Chi non capisce perché è bloccato apre un ticket.

Dove metto i permessi?

Sul server. Il client può nascondere un controllo, ma la decisione su chi può cambiare un valore va presa e verificata lato server.

Posso usare i blocchi settings del catalogo?

Puoi leggerli e adattarli, ma la licenza è dichiarata «see source». Controlla il repository indicato prima di includerli in un prodotto.