Passer au contenu principal

Qu’est-ce que React ?

React est une bibliothèque JavaScript développée et maintenue par Meta (ex-Facebook) pour construire des interfaces utilisateur. Elle se caractérise par une écriture d’UI déclarative et une architecture basée sur des composants, utilisée aussi bien pour de petits widgets que pour des SPA complètes. Le cœur de React est le rendu efficace via un DOM virtuel. Quand l’état (state) change, React applique uniquement le diff au DOM, sans que vous manipuliez le DOM manuellement.
Cette page traite de React 19 combiné à Inertia v3. Les starter kits Laravel 13 utilisent cette configuration par défaut.

JSX et TSX

Les composants React s’écrivent en JSX (JavaScript XML), une syntaxe proche du HTML directement intégrée à JavaScript.
// Exemple JSX
function Greeting({ name }) {
    return <h1>Bonjour {name}</h1>
}
Les starter kits utilisent TypeScript (.tsx) par défaut. Les définitions de types renforcent la complétion IDE et permettent de détecter les bugs plus tôt.
// Exemple TSX (TypeScript)
type Props = {
    name: string
}

function Greeting({ name }: Props) {
    return <h1>Bonjour {name}</h1>
}
Le starter kit React de Laravel utilise TypeScript + TSX par défaut. Tous les exemples de cette page sont écrits en TSX.

Place de React dans Laravel

Historique

React est arrivé un peu plus tard que Vue dans l’histoire de Laravel, mais il est aujourd’hui à parité ou davantage. Avec Laravel 6 (2019), le scaffold d’authentification est extrait dans le package laravel/ui et une version React est proposée en même temps que Vue. À l’époque, Vue reste dominant et React est en retrait. Avec Laravel Breeze (2021), la stack Inertia + React est ajoutée, ce qui marque le vrai début de son adoption. La refonte des starter kits de Laravel 12 (2025) met React totalement à égalité avec Vue (voire en tête dans l’affichage).

Style dominant actuel : Inertia × React

Aujourd’hui, l’usage principal de React avec Laravel se fait via Inertia × React. Inertia réalise une architecture « monolithe moderne » où l’on passe directement des données du contrôleur Laravel à un composant React, sans concevoir d’API.

Mise en place

Via un starter kit (recommandé)

Pour un nouveau projet, le plus simple est de partir d’un starter kit.
laravel new my-app
En choisissant React dans le prompt interactif, tout est configuré automatiquement :
  • inertiajs/inertia-laravel (adaptateur serveur)
  • @inertiajs/react (adaptateur client)
  • react + react-dom (React 19)
  • @vitejs/plugin-react (plugin Vite)
  • TypeScript + @types/react
  • Tailwind CSS + shadcn/ui
  • Middleware HandleInertiaRequests
  • Écrans d’authentification (login, register, etc.) déjà implémentés en Inertia + React + TypeScript

Installation manuelle

Pour un projet existant, installez côté serveur et côté client séparément.
# Côté serveur (PHP)
composer require inertiajs/inertia-laravel

# Côté client (JavaScript)
npm install @inertiajs/react react react-dom
npm install --save-dev @vitejs/plugin-react @types/react @types/react-dom typescript
Ajoutez ensuite le plugin React à 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(),
    ],
})
Démarrez l’application Inertia dans 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} />)
    },
})
Pour les détails d’une installation manuelle (template racine, enregistrement du middleware, etc.), consultez la documentation officielle Inertia.

Structure des répertoires

Dans le starter kit, les composants de page React se trouvent dans resources/js/pages/.
resources/js/
├── app.tsx            # Point d'entrée de l'application Inertia
├── bootstrap.ts
├── components/        # Composants UI réutilisables
│   ├── ui/            # Composants shadcn/ui
│   └── ...
├── hooks/             # Hooks React personnalisés
├── layouts/           # Composants de layout
│   ├── app-layout.tsx
│   └── auth-layout.tsx
├── lib/               # Utilitaires et configuration
├── pages/             # Composants de page Inertia (correspondent au nom de contrôleur)
│   ├── auth/
│   │   ├── login.tsx
│   │   └── register.tsx
│   ├── dashboard.tsx
│   └── posts/
│       ├── index.tsx
│       ├── create.tsx
│       └── show.tsx
└── types/             # Définitions de types TypeScript
Inertia::render('posts/index', [...]) correspond au composant resources/js/pages/posts/index.tsx.

Bases de la syntaxe JSX

React utilise JSX : une syntaxe façon HTML au sein de JavaScript. Voici les patrons minimaux à connaître pour lire le code des starter kits.

{} — interpolation de variable

Dans JSX, {} permet d’injecter une valeur ou une expression JavaScript.
const name = 'le monde'
const count = 3

return (
    <div>
        <p>Bonjour, {name} !</p>
        <p>Le double : {count * 2}</p>
    </div>
)

Branchement — && et opérateur ternaire

React n’a pas d’équivalent de v-if. Pour un simple test, utilisez &&. Pour un if/else, l’opérateur ternaire ? :.
{/* Affiché uniquement si vrai */}
{isLoggedIn && <p>Bienvenue !</p>}

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

{/* if / else if / else */}
{isLoggedIn
    ? <p>Bienvenue !</p>
    : role === 'admin'
        ? <p>Connecté en tant qu'administrateur</p>
        : <a href="/login">Connexion</a>
}

Rendu de liste — .map()

Utilisez Array.map() pour rendre une liste. Précisez toujours la prop key pour un diff efficace.
<ul>
    {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
    ))}
</ul>
C’est l’équivalent du v-for :key de Vue et du {#each} de Svelte.

className — nom de classe CSS

JSX étant compilé en JavaScript, class est un mot-clé réservé. Pour les classes CSS, utilisez className.
{/* HTML: <div class="container"> */}
<div className="container">...</div>

Handlers d’événements — camelCase

Les attributs d’événements JSX sont en camelCase et reçoivent une référence de fonction.
<button onClick={handleClick}>Cliquer</button>

{/* Annuler le comportement par défaut de submit */}
<form onSubmit={(e) => { e.preventDefault(); handleSubmit() }}>
    ...
</form>

Composant de page — bases

Un composant de page Inertia est un composant React classique. Les données passées depuis le contrôleur Laravel arrivent en props.

Contrôleur

// 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),
        ]);
    }
}

Composant de page 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) renvoie aussi les infos de pagination
        // current_page, last_page, per_page, total, etc.
    }
}

export default function PostsIndex({ posts }: Props) {
    return (
        <div>
            <h1>Liste des articles</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>
    )
}
Il suffit de recevoir les props en argument du composant pour utiliser les données transmises par le contrôleur. Aucune API REST à définir.
Le composant <Link> fourni par @inertiajs/react déclenche la navigation en XHR, évitant le rechargement complet du navigateur.
import { Link } from '@inertiajs/react'

export default function PostsIndex() {
    return (
        <div>
            {/* Lien de base */}
            <Link href="/posts">Liste des articles</Link>

            {/* Lien POST (suppression, etc.) */}
            <Link href="/posts/1" method="delete" as="button">
                Supprimer
            </Link>

            {/* Préchargement (préfetch au survol) */}
            <Link href="/posts/1" preload>
                Voir l'article
            </Link>
        </div>
    )
}
L’écriture est identique à celle d’une balise <a> classique, mais Inertia remplace uniquement le composant de page, offrant une expérience de type SPA.

Composant Form

Le composant <Form> de @inertiajs/react est le style recommandé d’envoi de formulaire utilisé dans les écrans d’authentification des starter kits. On passe action et method en props et on récupère errors et processing via une fonction children (render prop).

Utilisation de 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">Titre</label>
                        <input id="title" name="title" type="text" required />
                        {errors.title && <p className="error">{errors.title}</p>}
                    </div>

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

                    <button type="submit" disabled={processing}>
                        {processing ? 'Envoi...' : 'Publier'}
                    </button>
                </>
            )}
        </Form>
    )
}
Le children de <Form> est une fonction ({ errors, processing }) => JSX (render prop). Le composant Form calcule et injecte ces valeurs. Les champs utilisent l’attribut natif name plutôt qu’un onChange, ce qui active la collecte standard des données de formulaire par le navigateur.

Patron des starter kits

Les starter kits utilisent Wayfinder pour gérer les routes en tant qu’objets. store.form() renvoie un objet contenant action et method que l’on spread dans <Form>.
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 }) => (
                <>
                    {/* Contenu du formulaire */}
                </>
            )}
        </Form>
    )
}
Les champs listés dans resetOnSuccess sont automatiquement réinitialisés après un envoi réussi. Utilisez-le pour les champs comme le mot de passe qu’il faut vider.
Sans Wayfinder, passer directement une URL (action="/login") fonctionne de la même façon.

Hook useForm

Pour la gestion de formulaire, utilisez le hook useForm de @inertiajs/react. Il simplifie la gestion d’état, l’envoi et l’affichage des erreurs de validation.

Côté contrôleur

// 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', 'Article créé.');
    }
}

Composant de formulaire 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>Titre</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>Contenu</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 ? 'Envoi...' : 'Publier'}
            </button>
        </form>
    )
}
Principales propriétés retournées par useForm.
Propriété / MéthodeDescription
dataObjet des données du formulaire
setData(field, value)Met à jour la valeur d’un champ
errorsErreurs de validation (par nom de champ)
processingtrue pendant l’envoi (pour désactiver le bouton)
isDirtytrue si la valeur diffère de l’initiale
post(url)Envoi en POST
put(url)Envoi en PUT (mise à jour)
delete(url)Envoi en DELETE
reset()Réinitialiser le formulaire aux valeurs initiales
En cas d’erreur de validation, useForm conserve les valeurs saisies tout en affichant les erreurs.

Données partagées (Shared Data)

Les données communes à toutes les pages (utilisateur connecté, messages flash) sont déclarées dans la méthode share() du 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'),
            ],
        ]);
    }
}
Depuis un composant React, accédez aux données partagées via le 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>Invité</span>
                )}
            </header>

            {flash.success && (
                <div className="alert-success">{flash.success}</div>
            )}
        </>
    )
}
Ces données étant présentes à chaque requête, limitez-les au strict nécessaire. Une évaluation paresseuse avec fn() ne les calcule qu’en cas d’accès effectif.

Bases des hooks React

Voici les hooks essentiels pour développer avec Inertia × React.

useState — état local

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)}>Basculer</button>
        </div>
    )
}

useEffect — effets de bord

import { useState, useEffect } from 'react'

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

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

        // Fonction de nettoyage
        return () => clearInterval(timer)
    }, []) // Tableau vide = exécuté une seule fois au montage

    return <p>Temps écoulé : {seconds} s</p>
}

useMemo et useCallback — optimisation de performance

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) {
    // Mémoïse une valeur (recalculée seulement si posts change)
    const publishedPosts = useMemo(
        () => posts.filter((post) => post.published),
        [posts],
    )

    // Mémoïse une fonction (recréée seulement si ses dépendances changent)
    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>
    )
}

Support TypeScript

Le starter kit React utilise TypeScript par défaut. Combiné aux types Inertia, il assure la sûreté des props.

Types globaux

Le starter kit définit le type des données partagées dans 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
    }
}

Utilisation du type dans un composant de page

import { PageProps } from '@/types'

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

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

Conclusion

React s’exprime particulièrement bien avec Laravel dans un « monolithe moderne » via Inertia. La bonne intégration avec TypeScript le rend adapté aux applications de grande taille.
ÉlémentRôle
Contrôleur LaravelRoutage, récupération de données, validation
Inertia::render()Transmet les données du contrôleur au composant React
Composant de page ReactReçoit les props et rend l’UI
useFormGestion d’état, envoi et erreurs de formulaire
Composant LinkNavigation sans rechargement complet
usePage().propsAccès aux données partagées
TypeScriptSûreté des props et meilleure complétion IDE
Utiliser Inertia × React combine la simplicité du backend Laravel à la puissance de l’écosystème React. En partant du starter kit, vous pouvez démarrer le développement immédiatement, écrans d’authentification inclus.

Documentation officielle Inertia.js

Retrouvez toutes les fonctionnalités d’Inertia v3 dans la documentation officielle.
Dernière modification le 13 juillet 2026