React

Tabs shadcn nello stato dell'URL

Tieni il valore di Tabs in un parametro dell'URL: il link diventa condivisibile, il refresh tiene la vista e il tasto indietro funziona.

In sintesi

  • Un parametro dell'URL al posto di useState rende la vista condivisibile e ricaricabile.
  • Il valore letto dall'URL va validato contro una lista chiusa, altrimenti l'utente può aprire una vista che non esiste.
  • router.replace non aggiunge voci alla cronologia, router.push sì: la scelta cambia il tasto indietro.

Risposta breve: tieni il valore di Tabs in un parametro dell'URL, non in useState. Il componente resta controllato, il link diventa condivisibile e il tasto indietro riporta la vista precedente.

Il componente

"use client";

import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";

const VIEWS = ["panoramica", "attivita", "fatture"] as const;
type View = (typeof VIEWS)[number];

function parseView(raw: string | null): View {
  return VIEWS.includes(raw as View) ? (raw as View) : "panoramica";
}

export function SettingsTabs() {
  const router = useRouter();
  const pathname = usePathname();
  const params = useSearchParams();
  const view = parseView(params.get("tab"));

  function onValueChange(next: string) {
    const query = new URLSearchParams(params.toString());
    query.set("tab", next);
    router.replace(pathname + "?" + query.toString(), { scroll: false });
  }

  return (
    
      
        {VIEWS.map((item) => (
          
            {item}
          
        ))}
      
      Contenuto della panoramica
      Contenuto delle attività
      Contenuto delle fatture
    
  );
}

Due dettagli rendono il componente stabile: value arriva sempre da parseView, quindi non è mai un valore fuori lista, e onValueChange riscrive solo il parametro tab senza perdere gli altri. Il resto della query sopravvive alla navigazione fra le viste.

Cosa cambia rispetto a useState

  • Link condivisibile: incolli l'indirizzo in chat e chi lo apre vede la stessa scheda, non la prima.
  • Refresh e cronologia: ricaricare non riporta alla vista iniziale, e il tasto indietro torna alla scheda precedente.
  • Primo render corretto: il server disegna già la vista giusta, quindi non c'è il passaggio dalla prima scheda a quella richiesta.

Il valore non valido

Un parametro scritto a mano può contenere qualsiasi cosa. parseView confronta il valore con la lista chiusa e ricade sul valore predefinito: senza questo controllo il componente mostra un pannello vuoto e l'utente non capisce perché.

Il comando

npx shadcn@latest add tabs

Per la parte visiva: la libreria shadcn, il componente expandable-tabs come variante animata, e i blocchi impostazioni che contengono schede già impaginate.

La limitazione

Ogni cambio scheda con replace non lascia traccia nella cronologia, quindi il tasto indietro salta la scheda precedente; con push ogni scheda diventa una voce e il tasto indietro si riempie. Nell'App Router la navigazione passa dal server, quindi su una pagina pesante il cambio scheda costa una richiesta: se i pannelli sono grandi, conviene montarli una volta e nasconderli.

Dove continuare

Il modello di distribuzione dei componenti è in che cos'è shadcn/ui; la struttura delle rotte è in struttura dell'App Router; il confronto con un'altra libreria è in shadcn/ui vs MUI. Per capire quanto costa un passaggio in più al server leggi INP e reattività.

Domande frequenti

Meglio replace o push per le schede?

Replace quando le schede sono viste della stessa pagina e non vuoi riempire la cronologia; push quando ogni scheda deve essere raggiungibile con il tasto indietro. La scelta cambia quello che l'utente si aspetta dal tasto.

Perché useSearchParams richiede Suspense?

Nell'App Router la lettura dei parametri è dinamica, quindi il componente che la usa va avvolto in Suspense se la pagina è statica, altrimenti la build segnala un errore di prerendering.

Posso usare Tabs senza Next.js?

Sì. Con React Router la stessa idea si costruisce con useSearchParams del router: leggi il parametro, valida il valore e aggiorna la query al cambio di scheda.

Il parametro deve avere un nome fisso?

Conviene: tab è corto, leggibile e non collide con i filtri. Un nome generico come view diventa ambiguo quando la pagina ha più di un gruppo di schede.

Lo stato nell'URL rompe l'indicizzazione?

No, se ogni parametro produce la stessa pagina canonica e non genera duplicati. Il canonical resta l'indirizzo senza query, a meno che la vista non sia un contenuto distinto.