> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Introducción a React — Fundamentos para usarlo con Inertia × Laravel

> Guía de introducción para usar React con Laravel + Inertia.js. Se explica de forma práctica desde el recorrido histórico desde laravel/ui hasta los starter kits actuales, así como los hooks de Inertia React: useForm, usePage, etc.

## ¿Qué es React?

React es una biblioteca de JavaScript para construir interfaces de usuario, desarrollada y mantenida por Meta (antes Facebook). Se caracteriza por su descripción declarativa de la UI y una arquitectura **basada en componentes**, y se utiliza en un amplio rango: desde pequeños widgets hasta SPAs completas.

El núcleo de React es el renderizado eficiente a través del **DOM virtual**. Cuando cambia el estado (state), React refleja en el DOM solo la diferencia, así que no hace falta manipular manualmente el DOM.

<Info>
  En esta página se explica la combinación de React 19 e Inertia v3. Los starter kits de Laravel 13 usan esta configuración por defecto.
</Info>

### JSX y TSX

Los componentes de React se escriben con la sintaxis **JSX** (JavaScript XML). Permite escribir una notación parecida a HTML directamente dentro de JavaScript.

```jsx theme={null}
// Ejemplo de JSX
function Greeting({ name }) {
    return <h1>Hola, {name}</h1>
}
```

En los starter kits se adopta **TypeScript** (`.tsx`) como estándar. Con las definiciones de tipo se mejora el autocompletado del IDE y se detectan bugs de forma temprana.

```tsx theme={null}
// Ejemplo de TSX (TypeScript)
type Props = {
    name: string
}

function Greeting({ name }: Props) {
    return <h1>Hola, {name}</h1>
}
```

<Tip>
  Los starter kits de React de Laravel usan TypeScript + TSX como estándar. Todos los ejemplos de esta página también están en TSX.
</Tip>

***

## Posición dentro de Laravel

### Historia

La relación entre React y Laravel es algo más reciente que la de Vue, pero actualmente tiene un tratamiento igual o superior.

```mermaid theme={null}
timeline
    title Recorrido de Laravel y React
    2019 : Laravel 6 — se añade el scaffold de React en laravel/ui
    2021 : Laravel 8 — Breeze añade el stack Inertia React
    2022 : Laravel 9 — Migración a Vite
    2025 : Laravel 12 — Renovación de los starter kits (React / Vue)
    2026 : Laravel 13 — Starter kits compatibles con Inertia v3
```

En **Laravel 6 (2019)** el scaffold de autenticación se separó al paquete `laravel/ui`, y junto con Vue se ofreció también un scaffold para React. Sin embargo, en aquel momento Vue era predominante y la presencia de la versión React era escasa.

Con la incorporación del stack Inertia + React en **Laravel Breeze (2021)** comenzó su adopción real, y con la renovación de los starter kits de **Laravel 12 (2025)**, React pasó a estar en pie de igualdad con Vue (más aún, apareciendo el primero).

### Estilo dominante actual: Inertia × React

El uso central de React en Laravel actualmente es **Inertia × React**. Inertia permite realizar una arquitectura de "monolito moderno" en la que se pasan datos directamente desde los controladores de Laravel a componentes de React sin diseñar una API.

```mermaid theme={null}
graph LR
    Browser["Navegador"]
    Inertia["Inertia.js<br>(capa de adaptador)"]
    Laravel["Laravel<br>(controlador)"]
    React["React<br>(componente de página)"]

    Browser <-->|XHR / carga de página completa| Inertia
    Inertia <-->|respuesta Inertia| Laravel
    Inertia -->|props| React
    React -->|renderiza| Browser
```

***

## Configuración

### Vía starter kit (recomendado)

Al iniciar un proyecto nuevo, usar el starter kit es lo más sencillo.

```shell theme={null}
laravel new my-app
```

Si eliges **React** en el prompt interactivo, se configura automáticamente todo lo siguiente.

* `inertiajs/inertia-laravel` (adaptador del lado servidor)
* `@inertiajs/react` (adaptador del lado cliente)
* `react` + `react-dom` (React 19)
* `@vitejs/plugin-react` (plugin de Vite)
* TypeScript + `@types/react`
* Tailwind CSS + biblioteca de componentes shadcn/ui
* Middleware `HandleInertiaRequests`
* Pantallas de autenticación como login y registro (ya implementadas con Inertia + React + TypeScript)

### Instalación manual

Si vas a añadirlo a un proyecto existente, instala por separado el lado servidor y el lado cliente.

```shell theme={null}
# Lado servidor (PHP)
composer require inertiajs/inertia-laravel

# Lado cliente (JavaScript)
npm install @inertiajs/react react react-dom
npm install --save-dev @vitejs/plugin-react @types/react @types/react-dom typescript
```

A continuación, añade el plugin de React en `vite.config.ts`.

```ts theme={null}
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(),
    ],
})
```

Arranca la app Inertia en `resources/js/app.tsx`.

```tsx theme={null}
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} />)
    },
})
```

<Info>
  Para los detalles de la instalación manual (configuración de la plantilla raíz, registro del middleware, etc.), consulta la [documentación oficial de Inertia](https://inertiajs.com/installation).
</Info>

***

## Estructura de directorios

En los starter kits, los componentes de página de React se colocan en el directorio `resources/js/pages/`.

```
resources/js/
├── app.tsx            # Punto de entrada de la app Inertia
├── bootstrap.ts
├── components/        # Componentes de UI reutilizables
│   ├── ui/            # Componentes shadcn/ui
│   └── ...
├── hooks/             # Hooks de React personalizados
├── layouts/           # Componentes de layout
│   ├── app-layout.tsx
│   └── auth-layout.tsx
├── lib/               # Utilidades y configuración
├── pages/             # Componentes de página de Inertia (corresponden al nombre del controlador)
│   ├── auth/
│   │   ├── login.tsx
│   │   └── register.tsx
│   ├── dashboard.tsx
│   └── posts/
│       ├── index.tsx
│       ├── create.tsx
│       └── show.tsx
└── types/             # Definiciones de tipo TypeScript
```

Cuando escribes `Inertia::render('posts/index', [...])`, el componente correspondiente es `resources/js/pages/posts/index.tsx`.

***

## Fundamentos de la sintaxis JSX

React usa JSX. Es un estilo en el que se escribe sintaxis parecida a HTML dentro de JavaScript. Presentamos los patrones mínimos que conviene conocer para leer el código de los starter kits.

#### `{}` — Interpolación de variables

Dentro de JSX se usa `{}` para incrustar valores o expresiones de JavaScript.

```tsx theme={null}
const name = 'mundo'
const count = 3

return (
    <div>
        <p>Hola, {name}!</p>
        <p>El doble es {count * 2}</p>
    </div>
)
```

#### Condicionales — `&&` y operador ternario

React no tiene una directiva equivalente a `v-if`. Para condicionales simples usa el operador `&&`, y para if/else, el operador ternario `? :`.

```tsx theme={null}
{/* Se muestra solo cuando la condición es true */}
{isLoggedIn && <p>¡Bienvenido!</p>}

{/* if / else */}
{isLoggedIn ? <p>¡Bienvenido!</p> : <a href="/login">Iniciar sesión</a>}

{/* if / else if / else */}
{isLoggedIn
    ? <p>¡Bienvenido!</p>
    : role === 'admin'
        ? <p>Sesión iniciada como administrador</p>
        : <a href="/login">Iniciar sesión</a>
}
```

#### Renderizado de listas — `.map()`

Para renderizar listas se usa `Array.map()`. Para permitir un diff eficiente, hay que indicar siempre la prop `key`.

```tsx theme={null}
<ul>
    {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
    ))}
</ul>
```

Es el equivalente a `v-for :key` de Vue o `{#each}` de Svelte.

#### `className` — Nombre de clase CSS

Como JSX se compila a JavaScript, `class` es palabra reservada. Para las clases CSS se usa `className`.

```tsx theme={null}
{/* HTML: <div class="container"> */}
<div className="container">...</div>
```

#### Manejadores de eventos — camelCase

Los atributos de evento en JSX se escriben en camelCase, y se les pasa la referencia a una función.

```tsx theme={null}
<button onClick={handleClick}>Clic</button>

{/* Cancelar el comportamiento por defecto del envío del formulario */}
<form onSubmit={(e) => { e.preventDefault(); handleSubmit() }}>
    ...
</form>
```

***

## Fundamentos del componente de página

Los componentes de página de Inertia son componentes React normales. Los datos pasados desde el controlador de Laravel se reciben como props.

### Controlador

```php theme={null}
// 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 de página React

```tsx theme={null}
// 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) devuelve un objeto que incluye también información de paginación
        // También se pueden usar current_page, last_page, per_page, total, etc.
    }
}

export default function PostsIndex({ posts }: Props) {
    return (
        <div>
            <h1>Lista de publicaciones</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>
    )
}
```

Solo con recibir las props como argumento del componente puedes usar tal cual los datos pasados desde el controlador. No hace falta definir una API REST.

***

## Componente `Link`

Al usar el componente `<Link>` que ofrece `@inertiajs/react`, la navegación entre páginas se hace por XHR y se evita la recarga completa del navegador.

```tsx theme={null}
import { Link } from '@inertiajs/react'

export default function PostsIndex() {
    return (
        <div>
            {/* Enlace básico */}
            <Link href="/posts">Lista de publicaciones</Link>

            {/* Enlace con método POST (para borrar, etc.) */}
            <Link href="/posts/1" method="delete" as="button">
                Borrar
            </Link>

            {/* Preload (precarga al pasar el ratón) */}
            <Link href="/posts/1" preload>
                Ver publicación
            </Link>
        </div>
    )
}
```

Se escribe igual que una etiqueta `<a>` normal, pero por debajo Inertia solo cambia el componente de página, con lo que se logra una sensación de operación como en una SPA.

***

## Componente `Form`

El componente `<Form>` que ofrece `@inertiajs/react` es el estilo recomendado de envío de formularios que se usa en las pantallas de autenticación del starter kit. Se indican `action` y `method` como props, y en la función children (render prop) se reciben `errors` y `processing`.

### Uso básico

```tsx theme={null}
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">Título</label>
                        <input id="title" name="title" type="text" required />
                        {errors.title && <p className="error">{errors.title}</p>}
                    </div>

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

                    <button type="submit" disabled={processing}>
                        {processing ? 'Enviando...' : 'Publicar'}
                    </button>
                </>
            )}
        </Form>
    )
}
```

El children de `<Form>` es una función del tipo `({ errors, processing }) => JSX` (patrón render prop). El componente `Form` calcula automáticamente esos valores y te los pasa. En los campos del formulario, en lugar de un handler `onChange`, se usa el atributo nativo `name` de HTML, y funciona la recolección estándar de datos de formulario del navegador.

### Patrón del starter kit

El starter kit gestiona las rutas como objetos usando [Wayfinder](/es/blog/wayfinder-introduction). `store.form()` devuelve un objeto que incluye `action` y `method` del objeto de ruta, y se aplica al `<Form>` con spread.

```tsx theme={null}
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 }) => (
                <>
                    {/* Contenido del formulario */}
                </>
            )}
        </Form>
    )
}
```

Los campos indicados en `resetOnSuccess` se resetean automáticamente al enviar con éxito. Se indican en campos que se quieren dejar vacíos tras el envío, como la contraseña.

<Info>
  Si no usas Wayfinder, pasando directamente la URL con `action="/login"` funciona igual.
</Info>

***

## Hook `useForm`

Para el procesamiento de formularios se usa el hook `useForm` de `@inertiajs/react`. La gestión del estado del formulario, el envío y la visualización de errores de validación se implementan de forma sencilla.

### Lado del controlador

```php theme={null}
// 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', 'Se ha creado la publicación.');
    }
}
```

### Componente de formulario React

```tsx theme={null}
// 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>Título</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>Contenido</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 ? 'Enviando...' : 'Publicar'}
            </button>
        </form>
    )
}
```

Resumen de las principales propiedades del objeto que devuelve `useForm`.

| Propiedad / Método      | Descripción                                             |
| ----------------------- | ------------------------------------------------------- |
| `data`                  | Objeto de datos del formulario                          |
| `setData(field, value)` | Actualiza el valor de un campo                          |
| `errors`                | Errores de validación (acceso por nombre de campo)      |
| `processing`            | `true` durante el envío (útil para desactivar el botón) |
| `isDirty`               | `true` si se ha cambiado respecto al valor inicial      |
| `post(url)`             | Enviar con petición POST                                |
| `put(url)`              | Enviar con petición PUT (actualización)                 |
| `delete(url)`           | Enviar con petición DELETE                              |
| `reset()`               | Resetear el formulario a los valores iniciales          |

Cuando se devuelve un error de validación, `useForm` mantiene el contenido introducido y muestra el error.

***

## Datos compartidos (Shared Data)

Los datos comunes a todas las páginas (información del usuario autenticado, mensajes flash, etc.) se definen en el método `share()` del middleware `HandleInertiaRequests`.

```php theme={null}
// 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'),
            ],
        ]);
    }
}
```

Para acceder a los datos compartidos desde un componente React se usa el hook `usePage()`.

```tsx theme={null}
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>Invitado</span>
                )}
            </header>

            {flash.success && (
                <div className="alert-success">{flash.success}</div>
            )}
        </>
    )
}
```

<Info>
  Como los datos compartidos se incluyen en todas las peticiones, se recomienda limitarlos al mínimo necesario. Si los pasas mediante una evaluación diferida con `fn()`, solo se evalúan cuando realmente se acceden.
</Info>

***

## Fundamentos de los hooks de React

Presentamos los hooks básicos de React que conviene conocer para desarrollar con Inertia × React.

### `useState` — Gestión de estado local

```tsx theme={null}
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` — Procesamiento de efectos secundarios

```tsx theme={null}
import { useState, useEffect } from 'react'

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

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

        // Función de limpieza
        return () => clearInterval(timer)
    }, []) // Array vacío = se ejecuta una sola vez en el mount

    return <p>Tiempo transcurrido: {seconds}s</p>
}
```

### `useMemo` y `useCallback` — Optimización de rendimiento

```tsx theme={null}
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) {
    // Memoiza el valor (no se recalcula mientras posts no cambie)
    const publishedPosts = useMemo(
        () => posts.filter((post) => post.published),
        [posts],
    )

    // Memoiza la función (no se recrea mientras las dependencias no cambien)
    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>
    )
}
```

***

## Soporte de TypeScript

La versión React de los starter kits usa TypeScript por defecto. Combinado con las definiciones de tipo de Inertia, se garantiza la seguridad de tipos de las props.

### Definiciones de tipo globales

En los starter kits, los tipos de los datos compartidos se definen en `resources/js/types/index.d.ts`.

```ts theme={null}
// 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 de tipos en el componente de página

```tsx theme={null}
import { PageProps } from '@/types'

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

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

***

## Conclusión

React saca su potencial al combinarse con Laravel, especialmente en la configuración de "monolito moderno" a través de Inertia. Su compatibilidad con TypeScript también es excelente, y encaja bien con el desarrollo de aplicaciones a gran escala.

| Elemento                   | Rol                                                                 |
| -------------------------- | ------------------------------------------------------------------- |
| Controlador de Laravel     | Enrutamiento, obtención de datos y validación                       |
| `Inertia::render()`        | Pasa datos del controlador al componente React                      |
| Componente de página React | Recibe props y renderiza la UI                                      |
| `useForm`                  | Gestión del estado del formulario, envío y visualización de errores |
| Componente `Link`          | Navegación entre páginas sin recarga completa                       |
| `usePage().props`          | Acceso a los datos compartidos                                      |
| TypeScript                 | Seguridad de tipos de props y mejor autocompletado del IDE          |

Con Inertia × React obtienes una experiencia de desarrollo que combina la sencillez del backend Laravel con el potente ecosistema de React. Si creas el proyecto con el starter kit, puedes empezar a desarrollar de inmediato con las pantallas de autenticación incluidas.

<Card title="Documentación oficial de Inertia.js" icon="book-open" href="https://inertiajs.com">
  Para todas las funcionalidades de Inertia v3, consulta la documentación oficial.
</Card>


## Related topics

- [Introducción a Svelte — Fundamentos para usarlo con Inertia × Laravel](/es/blog/svelte-introduction.md)
- [Introducción a Vue.js — Fundamentos para usarlo con Inertia × Laravel](/es/blog/vue-introduction.md)
- [Frontend](/es/frontend.md)
- [Construir una SPA con Inertia.js](/es/blog/inertia-introduction.md)
- [Introducción a Laravel Nightwatch](/es/blog/nightwatch-introduction.md)
