π― Best Practices - Elerama Frontend β
Questo documento raccoglie tutte le best practices aggiornate del progetto.
π‘ Data Fetching β
β USA TanStack Query (Raccomandato) β
Per tutte le chiamate API, usa TanStack Query:
// β
RACCOMANDATO - TanStack Query
import { useCompaniesList } from "@/hooks/api/auth/useAuthApi";
const { data, isLoading, error, refetch } = useCompaniesList();
// Nessun useEffect, nessun useRef necessario!
// Cache automatica (5 min), de-duplicazione, StrictMode safeVantaggi:
- β Cache automatica (5 minuti default, configurabile)
- β StrictMode safe - De-duplicazione automatica delle richieste
- β
Zero boilerplate - Nessun
useEffect/useRefnecessario - β Invalidazioni coordinate - Mutations aggiornano automaticamente le query correlate
- β Override flessibile - Configurazione personalizzabile per ogni hook
β NON Usare useRef per Fetch β
Con TanStack Query, NON serve piΓΉ useRef per prevenire doppi fetch in StrictMode:
// β VECCHIO - Pattern deprecato
const hasFetchedRef = useRef(false);
useEffect(() => {
if (!hasFetchedRef.current) {
hasFetchedRef.current = true;
fetchData();
}
}, []);
// β
NUOVO - TanStack Query gestisce tutto automaticamente
const { data } = useCompaniesList();Quando usare useRef:
- β BroadcastChannel (non-fetch use cases)
- β
Riferimenti DOM (
const inputRef = useRef<HTMLInputElement>(null)) - β Valori che non devono causare re-render
- β MAI per prevenire doppi fetch con TanStack Query
π Documentazione TanStack Query β
- Guida Completa - Documentazione dettagliata (configurazione, hooks, best practices, troubleshooting)
π¦ Import Policy β
β NO Barrel Files β
NON creare mai file index.ts per re-export. Usa sempre import diretti:
// β
Corretto - Import diretto
import { useLogin } from "@/hooks/api/auth/useAuthApi";
import { authQueryKeys } from "@/hooks/api/auth/auth.query-keys";
import { invalidateAuthQueries } from "@/hooks/api/auth/auth.query-helpers";
// β Sbagliato - Barrel file non esiste
import { useLogin } from "@/hooks/api/auth"; // β No index.tsMotivazione: Import diretti garantiscono:
- TracciabilitΓ del codice
- Chiarezza delle dipendenze
- Migliore tree-shaking
- Nessuna ambiguitΓ
π Route Protection β
Protezione Base (Solo Autenticazione) β
import { createProtectedLoader } from "@/lib/auth/loaders";
// β
Una riga per proteggere
export const clientLoader = createProtectedLoader();
export default function Dashboard() {
return <div>Contenuto protetto</div>;
}Protezione con Settings (Autenticazione + Dati Utente) β
import { createProtectedLoader } from "@/lib/auth/loaders";
// β
Verifica autenticazione + presenza settings
// Se i settings non sono presenti, reindirizza a /welcome
export const clientLoader = createProtectedLoader({ requireSettings: true });
export default function Profile() {
const userSettings = useAuthStore((state) => state.userSettings);
// Qui sei sicuro che userSettings non Γ¨ null
return <div>Profilo</div>;
}Route Guest-Only (Solo Non Autenticati) β
import { createGuestLoader } from "@/lib/auth/loaders";
// β
Reindirizza utenti giΓ autenticati
export const clientLoader = createGuestLoader();
export default function Login() {
return <div>Login</div>;
}Route Pubbliche (Accessibili a Tutti) β
// β
Nessun clientLoader = accessibile a tutti
export default function About() {
return <div>Chi Siamo</div>;
}π Documentazione: Autenticazione
βοΈ React Compiler β
Non Usare Memoizzazione di Default β
React Compiler ottimizza automaticamente. NON usare useCallback, useMemo, React.memo di default:
// β Non necessario - React Compiler ottimizza automaticamente
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
// β
Lascia fare al Compiler
const handleClick = () => {
console.log("Clicked");
};Usa Memoizzazione SOLO Quando Necessario β
USE ONLY when:
- Funzioni usate come dipendenze di
useEffect - Props passate a componenti third-party non compilati
- Scenari critici identificati con profiling
- Valori di Context che non devono causare re-render
// β
OK - useEffect dependency
const fetchData = useCallback(async () => {
// API call
}, [dependency]);
useEffect(() => {
fetchData();
}, [fetchData]);π Documentazione: react-compiler-verification.md
π§ͺ Testing β
Test con TanStack Query β
Wrappa i test con QueryClientProvider:
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { renderHook, waitFor } from "@testing-library/react";
test("useCompaniesList dovrebbe caricare le aziende", async () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
},
});
const { result } = renderHook(() => useCompaniesList(), {
wrapper: ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
),
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toBeDefined();
});π Documentazione: testing.md
π¨ Component Patterns β
Esempio Completo: Pagina con Data Fetching β
import { createProtectedLoader } from "@/lib/auth/loaders";
import { AuthenticatedLayout } from "@/components/layout/authenticated-layout";
import { useCompaniesList } from "@/hooks/api/auth/useAuthApi";
// β
Protezione route
export const clientLoader = createProtectedLoader();
export default function Companies() {
// β
TanStack Query - cache automatica
const { data, isLoading, error, refetch } = useCompaniesList();
if (isLoading) {
return (
<AuthenticatedLayout>
<div>Caricamento...</div>
</AuthenticatedLayout>
);
}
if (error) {
return (
<AuthenticatedLayout>
<div>
<p>Errore: {error.message}</p>
<button onClick={() => refetch()}>Riprova</button>
</div>
</AuthenticatedLayout>
);
}
const companies = data?.data || [];
return (
<AuthenticatedLayout>
<div className="p-8">
<h1 className="text-3xl font-bold mb-4">Aziende</h1>
<ul className="space-y-2">
{companies.map((company) => (
<li key={company.id} className="p-4 border rounded">
{company.name}
</li>
))}
</ul>
</div>
</AuthenticatedLayout>
);
}π Sincronizzazione Multi-Tab β
Zustand Persist (Storage Events) β
Sincronizzazione automatica dello stato tra tab tramite localStorage:
import { useAuthStore } from "@/store/auth/auth.store";
// Accesso ottimizzato con selector
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const userSettings = useAuthStore((state) => state.userSettings);BroadcastChannel (Navigazione) β
Per azioni che richiedono navigazione coordinata tra tab:
import { useBroadcastChannel } from "@/hooks/iframe/useBroadcastChannel";
const { sendMessage } = useBroadcastChannel("auth", (message) => {
if (message.type === "logout") {
navigate("/");
}
});
// Logout con broadcast alle altre tab
const handleLogout = () => {
authStore.getState().logout();
sendMessage({ type: "logout" });
navigate("/");
};π Documentazione: useBroadcastChannel.md
π Documentation Policy β
β NON Creare β
- Documenti di riepilogo per bug fix
- Spiegazioni di singole modifiche
- Change logs manuali
β Aggiorna Sempre β
- Documentazione esistente quando le feature cambiano
- Test quando il codice cambia
- README quando cambia la struttura del progetto
π Quick Checklist β
Quando crei una nuova feature:
- [ ] Route protection: Usa
createProtectedLoader()se necessario - [ ] Data fetching: Usa TanStack Query hooks
- [ ] NO useRef per fetch: TanStack Query gestisce StrictMode
- [ ] Import diretti: NO barrel files (
index.ts) - [ ] NO memoizzazione: Lascia fare a React Compiler (salvo casi specifici)
- [ ] Test coverage: Scrivi test con
QueryClientProviderwrapper - [ ] TypeScript strict: Tutti i tipi devono essere espliciti
- [ ] Update docs: Aggiorna documentazione esistente se necessario
π Link Utili β
- Autenticazione
- TanStack Query Guide
- Quick Start Guide
- React Compiler Verification
- Testing Guide
- Zustand Store
Ultima revisione: 2025-11-10 Versione TanStack Query: v5.90.7 React: 19 React Router: 7.9