Vai al contenuto principale

Cos’è React

React è una libreria JavaScript per costruire interfacce utente, sviluppata e mantenuta da Meta (ex Facebook). È caratterizzata da una descrizione dichiarativa della UI e da un’architettura basata sui componenti, e viene usata da piccoli widget a intere SPA. Il cuore di React è il DOM virtuale, che consente re-render efficienti. Quando lo state cambia, React riflette nel DOM solo le differenze, senza che sia necessario manipolarlo manualmente.
In questa pagina spieghiamo la combinazione di React 19 e Inertia v3. Lo starter kit di Laravel 13 usa questa configurazione per impostazione predefinita.

JSX e TSX

I componenti React si scrivono con la sintassi JSX (JavaScript XML). Puoi scrivere direttamente dentro JavaScript una notazione simile all’HTML.
// Esempio di JSX
function Greeting({ name }) {
    return <h1>Ciao, {name}</h1>
}
Negli starter kit è adottato di default TypeScript (.tsx). Le definizioni di tipo rafforzano il completamento dell’IDE e ti fanno trovare i bug in anticipo.
// Esempio TSX (TypeScript)
type Props = {
    name: string
}

function Greeting({ name }: Props) {
    return <h1>Ciao, {name}</h1>
}
Lo starter kit React di Laravel usa TypeScript + TSX come standard. Anche gli esempi di questa pagina sono tutti in TSX.

Posizione in Laravel

Storia

Il rapporto tra React e Laravel è un po’ più recente rispetto a Vue, ma oggi è del tutto paragonabile. Con Laravel 6 (2019) lo scaffold di autenticazione è stato scorporato nel pacchetto laravel/ui e sono stati offerti scaffold sia per Vue sia per React. All’epoca però Vue era dominante e la presenza di React era limitata. Con l’aggiunta dello stack Inertia + React a Laravel Breeze (2021) è iniziata un’adozione seria; con il rinnovo degli starter kit di Laravel 12 (2025) React è stato trattato completamente allo stesso livello di Vue (anzi, viene mostrato per primo).

Stile principale attuale: Inertia × React

Il modo principale di usare React in Laravel oggi è Inertia × React. Inertia realizza un’architettura “monolite moderno” in cui puoi passare dati dai controller Laravel direttamente ai componenti React senza progettare un’API.

Setup

Tramite starter kit (consigliato)

Per iniziare un nuovo progetto, il modo più semplice è usare lo starter kit.
laravel new my-app
Se scegli React nel prompt interattivo, viene tutto configurato automaticamente.
  • inertiajs/inertia-laravel (adapter lato server)
  • @inertiajs/react (adapter lato client)
  • react + react-dom (React 19)
  • @vitejs/plugin-react (plugin Vite)
  • TypeScript + @types/react
  • Tailwind CSS + libreria di componenti shadcn/ui
  • Middleware HandleInertiaRequests
  • Schermate di autenticazione (login, registrazione, ecc.) già implementate con Inertia + React + TypeScript

Installazione manuale

Se vuoi aggiungerlo a un progetto esistente, installa separatamente lato server e lato client.
# Lato server (PHP)
composer require inertiajs/inertia-laravel

# Lato client (JavaScript)
npm install @inertiajs/react react react-dom
npm install --save-dev @vitejs/plugin-react @types/react @types/react-dom typescript
Aggiungi poi il plugin React in vite.config.ts.
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import react from '@vitejs/plugin-react'

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.tsx'],
            refresh: true,
        }),
        react(),
    ],
})
Avvia l’app Inertia in resources/js/app.tsx.
import { createInertiaApp } from '@inertiajs/react'
import { createRoot } from 'react-dom/client'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'

createInertiaApp({
    resolve: (name) =>
        resolvePageComponent(
            `./pages/${name}.tsx`,
            import.meta.glob('./pages/**/*.tsx'),
        ),
    setup({ el, App, props }) {
        createRoot(el).render(<App {...props} />)
    },
})
Per i dettagli dell’installazione manuale (configurazione del template root, registrazione dei middleware, ecc.) consulta la documentazione ufficiale di Inertia.

Struttura delle directory

Negli starter kit i componenti pagina React vanno nella directory resources/js/pages/.
resources/js/
├── app.tsx            # Entry point dell'app Inertia
├── bootstrap.ts
├── components/        # Componenti UI riutilizzabili
│   ├── ui/            # Componenti shadcn/ui
│   └── ...
├── hooks/             # Hook React personalizzati
├── layouts/           # Componenti di layout
│   ├── app-layout.tsx
│   └── auth-layout.tsx
├── lib/               # Funzioni utility e configurazione
├── pages/             # Componenti pagina Inertia (corrispondono ai nomi dei controller)
│   ├── auth/
│   │   ├── login.tsx
│   │   └── register.tsx
│   ├── dashboard.tsx
│   └── posts/
│       ├── index.tsx
│       ├── create.tsx
│       └── show.tsx
└── types/             # Definizioni di tipo TypeScript
Scrivendo Inertia::render('posts/index', [...]), il componente corrispondente è resources/js/pages/posts/index.tsx.

Basi della sintassi JSX

React usa JSX. Ecco i pattern minimi da conoscere per leggere il codice degli starter kit, uno stile in cui si scrive sintassi simile all’HTML dentro JavaScript.

{} — espansione di variabile

In JSX si usa {} per incorporare valori o espressioni JavaScript.
const name = 'mondo'
const count = 3

return (
    <div>
        <p>Ciao, {name}!</p>
        <p>Il doppio è {count * 2}</p>
    </div>
)

Condizionali — && e operatore ternario

React non ha una direttiva equivalente a v-if. Per condizioni semplici si usa l’operatore &&, per if/else l’operatore ternario ? :.
{/* Mostra solo se la condizione è true */}
{isLoggedIn && <p>Benvenuto!</p>}

{/* if / else */}
{isLoggedIn ? <p>Benvenuto!</p> : <a href="/login">Login</a>}

{/* if / else if / else */}
{isLoggedIn
    ? <p>Benvenuto!</p>
    : role === 'admin'
        ? <p>Loggato come amministratore</p>
        : <a href="/login">Login</a>
}

Rendering di liste — .map()

Per il rendering di liste si usa Array.map(). Specifica sempre la prop key per un aggiornamento differenziale efficiente.
<ul>
    {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
    ))}
</ul>
Corrisponde a v-for :key di Vue e a {#each} di Svelte.

className — nome della classe CSS

Poiché JSX viene compilato in JavaScript, class è una parola riservata. Per le classi CSS si usa className.
{/* HTML: <div class="container"> */}
<div className="container">...</div>

Gestori di eventi — camelCase

Gli attributi evento in JSX sono in camelCase e ricevono un riferimento a una funzione.
<button onClick={handleClick}>Clicca</button>

{/* Annulla il comportamento predefinito di submit */}
<form onSubmit={(e) => { e.preventDefault(); handleSubmit() }}>
    ...
</form>

Le basi del componente pagina

I componenti pagina di Inertia sono normali componenti React. I dati passati dal controller Laravel vengono ricevuti come props.

Controller

// app/Http/Controllers/PostController.php
use Inertia\Inertia;
use App\Models\Post;

class PostController extends Controller
{
    public function index()
    {
        return Inertia::render('posts/index', [
            'posts' => Post::latest()->paginate(10),
        ]);
    }
}

Componente pagina React

// resources/js/pages/posts/index.tsx
import { Link } from '@inertiajs/react'

type Post = {
    id: number
    title: string
    created_at: string
}

type Props = {
    posts: {
        data: Post[]
        // paginate(10) restituisce un oggetto con le informazioni di paginazione
        // Sono disponibili anche current_page, last_page, per_page, total, ecc.
    }
}

export default function PostsIndex({ posts }: Props) {
    return (
        <div>
            <h1>Elenco dei post</h1>
            {posts.data.map((post) => (
                <article key={post.id}>
                    <h2>
                        <Link href={`/posts/${post.id}`}>{post.title}</Link>
                    </h2>
                    <p>{post.created_at}</p>
                </article>
            ))}
        </div>
    )
}
Ricevendo le props come argomento del componente, puoi usare direttamente i dati passati dal controller. Non serve definire un’API REST.
Usando il componente <Link> di @inertiajs/react, le transizioni di pagina avvengono via XHR ed eviti il full reload del browser.
import { Link } from '@inertiajs/react'

export default function PostsIndex() {
    return (
        <div>
            {/* Link di base */}
            <Link href="/posts">Elenco post</Link>

            {/* Link con metodo POST (ad es. per l'eliminazione) */}
            <Link href="/posts/1" method="delete" as="button">
                Elimina
            </Link>

            {/* Preload (fetch anticipato all'hover) */}
            <Link href="/posts/1" preload>
                Vedi il post
            </Link>
        </div>
    )
}
Si scrive esattamente come un normale tag <a>, ma dietro le quinte Inertia sostituisce solo il componente di pagina, dando una sensazione da SPA.

Componente Form

Il componente <Form> fornito da @inertiajs/react è lo stile consigliato per l’invio dei form, usato nelle schermate di autenticazione degli starter kit. Specifichi action e method come props e ricevi errors e processing tramite una funzione children (render prop).

Uso di base

import { Form } from '@inertiajs/react'

export default function PostCreate() {
    return (
        <Form action="/posts" method="post" className="flex flex-col gap-4">
            {({ errors, processing }) => (
                <>
                    <div>
                        <label htmlFor="title">Titolo</label>
                        <input id="title" name="title" type="text" required />
                        {errors.title && <p className="error">{errors.title}</p>}
                    </div>

                    <div>
                        <label htmlFor="content">Contenuto</label>
                        <textarea id="content" name="content" />
                        {errors.content && <p className="error">{errors.content}</p>}
                    </div>

                    <button type="submit" disabled={processing}>
                        {processing ? 'Invio in corso...' : 'Pubblica'}
                    </button>
                </>
            )}
        </Form>
    )
}
Il children di <Form> è una funzione ({ errors, processing }) => JSX (pattern render prop). Il componente Form calcola e passa automaticamente questi valori. Ai campi del form si assegna l’attributo nativo HTML name invece di un handler onChange, così la raccolta standard dei dati del browser funziona.

Pattern degli starter kit

Gli starter kit gestiscono le rotte come oggetti tramite Wayfinder. store.form() restituisce un oggetto che contiene action e method dell’oggetto rotta e lo si passa a <Form> con lo spread.
import { Form } from '@inertiajs/react'
import { store } from '@/routes/login'

export default function Login() {
    return (
        <Form
            {...store.form()}
            resetOnSuccess={['password']}
            className="flex flex-col gap-6"
        >
            {({ errors, processing }) => (
                <>
                    {/* Contenuto del form */}
                </>
            )}
        </Form>
    )
}
I campi specificati in resetOnSuccess vengono ripristinati automaticamente in caso di invio riuscito. Impostalo per campi come la password che vuoi svuotare dopo l’invio.
Se non usi Wayfinder puoi passare direttamente l’URL come action="/login" e funziona allo stesso modo.

Hook useForm

Per la gestione dei form si usa l’hook useForm di @inertiajs/react. Puoi implementare in modo semplice gestione dello stato, invio e visualizzazione degli errori di validazione.

Lato controller

// app/Http/Controllers/PostController.php
class PostController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'title'   => ['required', 'string', 'max:255'],
            'content' => ['required', 'string'],
        ]);

        Post::create($validated + ['user_id' => auth()->id()]);

        return redirect()->route('posts.index')
            ->with('success', 'Post creato.');
    }
}

Componente form React

// resources/js/pages/posts/create.tsx
import { useForm } from '@inertiajs/react'
import { FormEventHandler } from 'react'

export default function PostCreate() {
    const { data, setData, post, processing, errors } = useForm({
        title: '',
        content: '',
    })

    const submit: FormEventHandler = (e) => {
        e.preventDefault()
        post('/posts')
    }

    return (
        <form onSubmit={submit}>
            <div>
                <label>Titolo</label>
                <input
                    type="text"
                    value={data.title}
                    onChange={(e) => setData('title', e.target.value)}
                />
                {errors.title && <p className="error">{errors.title}</p>}
            </div>

            <div>
                <label>Contenuto</label>
                <textarea
                    value={data.content}
                    onChange={(e) => setData('content', e.target.value)}
                />
                {errors.content && <p className="error">{errors.content}</p>}
            </div>

            <button type="submit" disabled={processing}>
                {processing ? 'Invio in corso...' : 'Pubblica'}
            </button>
        </form>
    )
}
Ecco le principali proprietà restituite da useForm.
Proprietà / MetodoDescrizione
dataOggetto dati del form
setData(field, value)Aggiorna il valore di un campo
errorsErrori di validazione (accesso per nome campo)
processingtrue durante l’invio (utile per disabilitare il pulsante)
isDirtytrue se ci sono modifiche rispetto ai valori iniziali
post(url)Invia con richiesta POST
put(url)Invia con richiesta PUT (update)
delete(url)Invia con richiesta DELETE
reset()Ripristina il form ai valori iniziali
Quando arrivano errori di validazione, useForm mostra gli errori mantenendo i contenuti inseriti.

Dati condivisi (Shared Data)

I dati necessari a tutte le pagine (utente loggato, messaggi flash, ecc.) si definiscono nel metodo share() del middleware HandleInertiaRequests.
// app/Http/Middleware/HandleInertiaRequests.php
use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'auth' => [
                'user' => $request->user()
                    ? $request->user()->only('id', 'name', 'email')
                    : null,
            ],
            'flash' => [
                'success' => $request->session()->get('success'),
                'error'   => $request->session()->get('error'),
            ],
        ]);
    }
}
Per accedere ai dati condivisi da un componente React, usa l’hook usePage().
import { usePage } from '@inertiajs/react'

type SharedProps = {
    auth: {
        user: { id: number; name: string; email: string } | null
    }
    flash: {
        success: string | null
        error: string | null
    }
}

export default function AppHeader() {
    const { auth, flash } = usePage<SharedProps>().props

    return (
        <>
            <header>
                {auth.user ? (
                    <span>{auth.user.name}</span>
                ) : (
                    <span>Ospite</span>
                )}
            </header>

            {flash.success && (
                <div className="alert-success">{flash.success}</div>
            )}
        </>
    )
}
Poiché i dati condivisi sono inclusi in ogni richiesta, si consiglia di limitarli allo stretto necessario. Usando valutazioni lazy con fn(), vengono valutati solo quando ci si accede davvero.

Basi degli hook React

Ecco gli hook React essenziali per sviluppare con Inertia × React.

useState — gestione dello stato locale

import { useState } from 'react'

export default function Counter() {
    const [count, setCount] = useState(0)
    const [isOpen, setIsOpen] = useState(false)

    return (
        <div>
            <p>{count}</p>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
        </div>
    )
}

useEffect — gestione degli effetti collaterali

import { useState, useEffect } from 'react'

export default function Timer() {
    const [seconds, setSeconds] = useState(0)

    useEffect(() => {
        const timer = setInterval(() => {
            setSeconds((s) => s + 1)
        }, 1000)

        // Funzione di cleanup
        return () => clearInterval(timer)
    }, []) // Array vuoto = eseguito una sola volta al mount

    return <p>Tempo trascorso: {seconds}s</p>
}

useMemo e useCallback — ottimizzazione delle prestazioni

import { useMemo, useCallback } from 'react'
import { router } from '@inertiajs/react'

type Post = { id: number; title: string; published: boolean }
type Props = { posts: Post[] }

export default function PostsList({ posts }: Props) {
    // Memoizza il valore (non ricalcola finché posts non cambia)
    const publishedPosts = useMemo(
        () => posts.filter((post) => post.published),
        [posts],
    )

    // Memoizza la funzione (non la ricrea finché non cambiano le dipendenze)
    const handleClick = useCallback((id: number) => {
        router.visit(`/posts/${id}`)
    }, [])

    return (
        <ul>
            {publishedPosts.map((post) => (
                <li key={post.id} onClick={() => handleClick(post.id)}>
                    {post.title}
                </li>
            ))}
        </ul>
    )
}

Supporto TypeScript

Lo starter kit React usa TypeScript di default. Combinandolo con le definizioni di tipo di Inertia puoi garantire la type-safety delle prop.

Definizioni di tipo globali

Negli starter kit i tipi dei dati condivisi si definiscono in resources/js/types/index.d.ts.
// resources/js/types/index.d.ts
export interface User {
    id: number
    name: string
    email: string
    email_verified_at?: string
}

export type PageProps<T extends Record<string, unknown> = Record<string, unknown>> = T & {
    auth: {
        user: User
    }
}

Uso dei tipi nel componente pagina

import { PageProps } from '@/types'

type Post = {
    id: number
    title: string
    content: string
}

export default function PostsIndex({ auth, posts }: PageProps<{ posts: Post[] }>) {
    return (
        <div>
            <p>Loggato come: {auth.user.name}</p>
            {posts.map((post) => (
                <article key={post.id}>
                    <h2>{post.title}</h2>
                </article>
            ))}
        </div>
    )
}

Conclusioni

React dà il meglio in combinazione con Laravel, in particolare nella configurazione “monolite moderno” tramite Inertia. È anche molto compatibile con TypeScript e adatto allo sviluppo di applicazioni di grandi dimensioni.
ElementoRuolo
Controller LaravelRouting, recupero dati e validazione
Inertia::render()Passa i dati dal controller al componente React
Componente pagina ReactRiceve le prop e renderizza la UI
useFormGestione stato, invio ed errori del form
Componente LinkTransizioni di pagina senza full reload
usePage().propsAccesso ai dati condivisi
TypeScriptType-safety delle prop e completamento IDE potenziato
Usando Inertia × React ottieni un’esperienza di sviluppo che combina la semplicità del backend Laravel con il potente ecosistema di React. Creando il progetto tramite starter kit, puoi iniziare subito con schermate di autenticazione incluse.

Documentazione ufficiale di Inertia.js

Per tutte le funzionalità di Inertia v3 consulta la documentazione ufficiale.
Ultima modifica il 13 luglio 2026