Courses

React.js + Inertia in Laravel 11: From Scratch

From React + API to React Inertia: Table of Data

In the first practical lesson, we will convert React + API project to Inertia.


React + API

First, the code "before": how does it look with React + API?

resources/views/js/components/Posts/Index.jsx:

import React, { useState, useEffect } from 'react';
import usePosts from '../../hooks/usePosts.js';
 
export default function PostsIndex() {
const { posts, getPosts } = usePosts();
 
useEffect(() => {
getPosts();
}, []);
 
return (
<div>
<table className="min-w-full divide-y divide-gray-200 border">
<thead>
<tr>
<th className="px-6 py-3 bg-gray-50 text-left">
<span className="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">ID</span>
</th>
<th className="px-6 py-3 bg-gray-50 text-left">
<span className="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Title</span>
</th>
<th className="px-6 py-3 bg-gray-50 text-left">
<span className="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Content</span>
</th>
<th className="px-6 py-3 bg-gray-50 text-left">
<span className="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Created At</span>
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200 divide-solid">
{posts && posts.data && posts.data.map((post) => (
<tr key={post.id}>
<td className="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{post.id}
</td>
<td className="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{post.title}
</td>
<td className="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{post.content}
</td>
<td className="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{post.created_at}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};

resources/views/js/hooks/usePosts.js:

import { useState } from 'react';
import axios from 'axios';
 
const usePosts = () => {
const [posts, setPosts] = useState({});
 
const getPosts = async () => {
try {
const response = await axios.get('/api/posts');
setPosts(response.data);
} catch (error) {
console.error('Error fetching posts:', error);
}
};
 
return { posts, getPosts };
};
 
export default usePosts;

app/Http/Controllers/PostController.php:

use App\Models\Post;
use App\Http\Resources\PostResource;
use Illuminate\Http\Resources\Json\JsonResource;
 
class PostController extends Controller
{
public function index(): JsonResource
{
return PostResource::collection(Post::all());
}
}

app/Http/Resources/PostResource.php:

class PostResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'content' => substr($this->content, 0, 50) . '...',
'created_at' => $this->created_at->toDateString(),
];
}
}

Before using Inertia, we must install it. Installation is for server-side and client-side. First, the server side.


Inertia: Server Side Installation

composer require inertiajs/inertia-laravel

Next, we must set up the root template. Because I used Laravel Breeze as the initial starter, I will replace the resources/views/layouts/app.blade.php with the content from the Inertia documentation.

resources/views/layouts/app.blade.php:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
@viteReactRefresh
@vite('resources/js/app.jsx')
@inertiaHead
</head>
<body>
@inertia
</body>
</html>

Now, styles won't be loaded. We can also add app.css to the @vite directive to load styles.

resources/views/layouts/app.blade.php:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
@vite('resources/js/app.js')
@vite(['resources/css/app.css', 'resources/js/app.jsx'])
@inertiaHead
</head>
<body>
@inertia
</body>
</html>

Lastly, we need to set up the Inertia middleware.

php artisan inertia:middleware

In Laravel 11, Middlewares are managed in the bootstrap/app.php file.

bootstrap/app.php:

use App\Http\Middleware\HandleInertiaRequests;
 
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
HandleInertiaRequests::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();

Now, let's set up the client side.


Inertia: Client Side Installation

Notice: React with React DOM must be installed already.

npm install @inertiajs/react

resources/js/app.js:

import './bootstrap';
 
import React from 'react';
import { createRoot } from 'react-dom/client';
import PostsIndex from './components/Posts/Index.jsx';
 
const reactComponents = {
'posts-index': PostsIndex,
};
 
document.querySelectorAll('[data-react-component]').forEach(domElement => {
const componentName = domElement.dataset.reactComponent;
const Component = reactComponents[componentName];
if (Component) {
const root = createRoot(domElement);
root.render(<Component />);
}
});
 
import { createInertiaApp } from '@inertiajs/react'
import { createRoot } from 'react-dom/client'
 
createInertiaApp({
resolve: name => {
const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true })
return pages[`./Pages/${name}.jsx`]
},
setup({ el, App, props }) {
createRoot(el).render(<App {...props} />)
},
})

Lastly, Inertia expects the root view to be in the resources/views folder. Because I used Breeze with the Blade, the root view is inside the resources/views/layouts folder. This can be easily changed.

app/Http/Middleware/HandleInertiaRequests.php:

class HandleInertiaRequests extends Middleware
{
protected $rootView = 'app';
protected $rootView = 'layouts.app';
 
// ...
}

Showing Table with Inertia

Now that we have Inertia installed, let's use it to show posts in the table. First, we must render inertia and pass the posts in the Controller.

app/Http/Controllers/PostController.php:

use Inertia\Inertia;
use Inertia\Response;
 
class PostController extends Controller
{
public function index(): JsonResource
public function index(): Response
{
$posts = Post::all();
 
return PostResource::collection(Post::all());
return Inertia::render('Posts/Index', compact('posts'));
}
}

Then, instead of resources/js/components, Inertia looks for pages inside resources/js/Pages. Instead of using a hooks now, the posts are passed using props in the React component.

resources/js/Pages/Posts/Index.jsx:

import React, { useState, useEffect } from 'react';
import usePosts from '../../hooks/usePosts.js';
 
export default function PostsIndex({ posts }) {
export default function PostsIndex() {
const { posts, getPosts } = usePosts();
 
useEffect(() => {
getPosts();
}, []);
 
return (
// ...
{posts && posts.data && posts.data.map((post) => (
{posts && posts && posts.map((post) => (
// ...
);
};

And the route to show posts.

routes/web.php:

use App\Http\Controllers\PostController;
 
Route::view('/', 'dashboard')->name('dashboard');
 
Route::get('posts', [PostController::class, 'index']);

If we visit the dashboard, we will get an error because we haven't changed anything to work with Inertia.

But, if we visit the posts page, we should see a table of the posts.

Of course, we don't have any design, and we lost some things implemented on the React, like shortening the content, but we do have the posts passed from the Controller, and it works in Inertia.


Conclusion: Benefits?

This is the main benefit and the primary goal of Inertia. You would stick with your logic to Laravel and get the data from Laravel and would not need to create separate API, separate hooks API in React, or something more complex.

This was the basic example. Let's move on and see what Inertia offers more.

avatar

Would have been nice to have started from scratch instead of converting an API version over.

Also maybe typo, not sure if meant to put view instead of Vue here

we lost some things implemented on the Vue

Still nice tutorial.

👍 1