Infinite Scroll with Inertia V2 using ReactJS


Prerequisite
Installed Laravel 11 project with React Inertia V2. If you don’t have, then you need to install it
laravel new infinite-scroll-app # Install laravel app
Would you like to install a starter kit? [No starter kit]:
[none ] No starter kit
[breeze ] Laravel Breeze
[jetstream] Laravel Jetstream
> breeze
Which Breeze stack would you like to install? [Blade with Alpine]:
[blade ] Blade with Alpine
[livewire ] Livewire (Volt Class API) with Alpine
[livewire-functional] Livewire (Volt Functional API) with Alpine
[react ] React with Inertia
[vue ] Vue with Inertia
[api ] API only
> react
Would you like any optional features? [None]:
[none ] None
[dark ] Dark mode
[ssr ] Inertia SSR
[typescript] TypeScript
[eslint ] ESLint with Prettier
> dark,ssr,eslint # You can choose what you need here
Which testing framework do you prefer? [Pest]:
[0] Pest
[1] PHPUnit
> 0
# Process
# Installing Laravel....
Which database will your application use? [MySQL]:
[mysql ] MySQL
[mariadb] MariaDB
[pgsql ] PostgreSQL
[sqlite ] SQLite (Missing PDO extension)
[sqlsrv ] SQL Server (Missing PDO extension)
> pgsql # I'll use postgresql here
Default database updated. Would you like to run the default database migrations? (yes/no) [yes]:
> no # Because we need to configure .env first
# Process
# Installing breeze..
cd infinite-scroll-app
Configure .env
to connect into our Database
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5433 # You can use 5432 for default port of pgsql
DB_DATABASE=infinite_scroll_app
DB_USERNAME=postgres
DB_PASSWORD=postgres
Generate Placeholder Data
You can totally use any kind of data for "infinite scroll." For example, I'm going to use posts data here.
php artisan make:model Post -mf # This will create a model, migration, and factory in one go
This is migration file should look like
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('slug')->unique();
$table->text('content');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('posts');
}
};
To avoid mass assignment problem we need to tell our model Post.php
which field is $fillable
or if all field is fillable except id
, then we can use $guarded
. You can learn the detail here
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/** @use HasFactory<\Database\Factories\PostFactory> */
use HasFactory;
protected $guarded = ['id'];
}
Next we need to create a dummy data. Laravel can easily make dummy data using factories. Open PostFactory.php
that we already create it earlier using php artisan
command, then we are using faker to generate random content (it already included in Laravel btw)
<?php
namespace Database\Factories;
use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
*/
class PostFactory extends Factory
{
protected $model = Post::class; # We can specify our models here (optional)
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'title' => $this->faker->sentence(5),
'slug' => $this->faker->slug(5),
'content' => $this->faker->text(),
];
}
}
Then generate 50 data using seeder, the seeder file is located in database/seeders/DatabaseSeeder.php
<?php
namespace Database\Seeders;
use App\Models\Post;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
Post::factory()->count(50)->create();
}
}
Apply Migration
After writing our models and prepare it using placeholder data via factory, then we can apply the migrations and seeding Post data
php artisan migrate
The database 'infinite_scroll_app' does not exist on the 'pgsql' connection.
Would you like to create it? (yes/no) [yes]
❯ yes # Because i dont have the database yet, So i create it
php artisan db:seed # Run DatabaseSeeder.php
Run the App
Because we use Laravel breeze starter kit, we should have auth (login or register) and dashboard pages. You can run the app by running this command
php artisan ser
# Open your second terminal
npm run dev
Visit http://localhost:8000/register for registering new user account, fill the form, and you could be able to visit dashboard page like this
Query Post data
On file routes/web.php
edit /dashboard
route callback to returning the Post
data. Best practices are by using controller, it is just only testing purposes in this tutorial
Route::get('/dashboard', function (Request $request) {
$perPage = 10; // You can also get it on $request->query()
$page = $request->query('page', 1);
$posts = Post::paginate($perPage); // Paginate to load lazy post data
$postPaginateProp = $posts->toArray(); // To access paginate property
$isNextPageExists = $postPaginateProp['current_page'] < $postPaginateProp['last_page'];
// Passing to the frontend
return Inertia::render('Dashboard', [
'posts' => Inertia::merge($posts->items()),
'page' => $page,
'isNextPageExists' => $isNextPageExists
]);
})->middleware(['auth', 'verified'])->name('dashboard');
You notice we use Inertia::merge
. This is useful if you need to merging latest data into previous props data. On first load we query just 10 Post data, then when are doing refetch in current route, so it will merge latest Post data (which is from 11 to 20) into our existing Post data before (1 to 10). Checkout the detail on Inertia merge documentation
Update Dashboard.jsx
to show all post data coming from inertia props.
import {
Head,
WhenVisible // Import <WhenVisible/> Inertia built in components
} from '@inertiajs/react';
export default function Dashboard(props) {
return (
...
{props.posts.map((post, idx) => (
<div
key={idx}
className="p-6 text-gray-900 dark:text-gray-100"
>
{post.title}
</div>
))}
{props.isNextPageExists && (
<WhenVisible
always
params={{
data: {
page: +props.page + 1,
},
only: ['posts', 'page', 'isNextPageExists'],
}}
fallback={<p>You reach the end.</p>}
>
<p>Loading...</p>
</WhenVisible>
)}
...
On this code, we use <WhenVisible></WhenVisible>
component with condition isNextPageExists
(you should understand what I mean by just reading the variable name). This component will be rendered on page when there is next page on post pagination property that we already define before. It will trigger when this element become visible on the viewport using IntersectionObserverAPI
. If there is next page of pagination, so it will make a request into current route (which is /dashboard
) to get latest post data based on params
object.
There is an option to define what props
to be updated using only
options in params
prop. Here we define posts
, page
, and isNextPageExists
props to be updated when triggered request is done.
By default, the WhenVisible
component will only trigger once when the element becomes visible. If you want to always trigger the data when the element is visible, you can provide the always
prop. Checkout this for more details
And finally you can see it infinitely scrollable if there is another post data available. Thanks for reading 😍, any suggestion would be appreciated
FYI: I’m currently learning to writing in English for my IELTS. I try my best to write this article without any Copy and Paste generated text from an AI. If there is any wrong grammar, feel free to correct me. This Post is going to be my witness for my future self that I’m willing to learn and growth 😊
Subscribe to my newsletter
Read articles from Adi Cahya Saputra directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Adi Cahya Saputra
Adi Cahya Saputra
Fullstack Developer | UI/UX Designer | Web & Mobile | NextJS, Laravel, ASP .NET Core, Kotlin, Flutter, React Native, Figma