Recursive logic is a concept in software development where you execute some sort of code in an infinite manner, such examples exist in trees data structure. Let’s learn in this post using Livewire components and laravel how to render a recursive component.
I suppose that you have a laravel 11 project and livewire 3 package already installed. We will create a navigation menu component that have items and sub-items.
First create a migration and model in terminal:
php artisan make:model NavMenu -m
This command creates a model and migration file. Open the migration located in data/migrations/ directory with filename ending with ‘create_nav_menus_table.php‘ and modify as below:
<?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('nav_menu', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->bigInteger('parent_id')->default(0);
$table->string('url');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('nav_menu');
}
};
To create the table run:
php artisan migrate
Next update the NavMenu model class add the $fillable property:
app/Models/NavMenu.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class NavMenu extends Model
{
use HasFactory;
protected $table = "nav_menu";
protected $fillable = [
"title",
"parent_id",
"url"
];
public function children()
{
return $this->hasMany(NavMenu::class, "parent_id");
}
}
As shown in this code i added a has_many relation children() between the parent_id column and the table own primary key. We will use it later in the view file to check if particular row have childrens.
We need some dummy data to work with, so we will create a seeder file:
php artisan make:seeder NavMenuSeeder
Update NavMenuSeeder.php like below:
<?php
namespace Database\Seeders;
use App\Models\NavMenu;
use Illuminate\Database\Seeder;
class NavMenuSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$menu = [
[
"id" => 1,
"title" => "Home",
"url" => "#",
"parent_id" => 0
],
[
"id" => 2,
"title" => "About us",
"url" => "#",
"parent_id" => 0
],
[
"id" => 3,
"title" => "Sub menu",
"url" => "#",
"parent_id" => 0
],
[
"id" => 4,
"title" => "Sub item 1",
"url" => "#",
"parent_id" => 3
],
[
"id" => 5,
"title" => "Sub item 2",
"url" => "#",
"parent_id" => 3
],
[
"id" => 6,
"title" => "Sub item 3",
"url" => "#",
"parent_id" => 3
],
[
"id" => 7,
"title" => "Sub sub item 1",
"url" => "#",
"parent_id" => 6
],
[
"id" => 8,
"title" => "Sub sub item 2",
"url" => "#",
"parent_id" => 6
],
[
"id" => 9,
"title" => "Sub sub item 3",
"url" => "#",
"parent_id" => 6
],
[
"id" => 10,
"title" => "Contact us",
"url" => "#",
"parent_id" => 0
],
];
foreach ($menu as $item) {
NavMenu::create($item);
}
}
}
Then call the NavMenuSeeder from DatabaseSeeder class
DatabaseSeeder.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call(NavMenuSeeder::class);
}
}
Execute the db:seed command in terminal:
php artisan db:seed
Now check the database table and see if the data inserted successfully.
Livewire Components
Let’s create two livewire component for our navigation menu:
php artisan livewire:make NavigationMenu
php artisan livewire:make MenuItem
Here i created two livewire components, the first one NavigationMenu will retrieve the menu items and the second one MenuItem will render a single menu item.
app/Livewire/NavigationMenu.php
<?php
namespace App\Livewire;
use App\Models\NavMenu;
use Livewire\Component;
class NavigationMenu extends Component
{
public function render()
{
$navMenu = NavMenu::where('parent_id', 0)->get();
return view('livewire.navigation-menu', compact('navMenu'));
}
}
In the render() method i retrieved the top menu items where parent_id=0 and send it to the view. Now open the navigation-menu view and update it with this code:
resources/views/livewire/navigation-menu.blade.php
<nav>
<ul>
@foreach($navMenu as $item)
<livewire:menu-item :item="$item" />
@endforeach
</ul>
</nav>
As shown we are iterating over $navMenu variable and display each item using <livewire:menu-item /> sending the :item as a prop.
Update app/Livewire/MenuItem.php as shown:
<?php
namespace App\Livewire;
use Livewire\Component;
class MenuItem extends Component
{
public $item;
public function render()
{
return view('livewire.menu-item');
}
}
I declared a public property $item which represents a single menu item. Open and update the menu-item view as below:
resources/views/livewire/menu-item.blade.php
<li class="inline {{$item->children->count() > 0? 'has-children':''}}">
<a href="{{$item->url}}">{{$item->title}}</a>
@if($item->children->count() > 0)
<ul>
@foreach($item->children as $subitem)
<livewire:menu-item :item="$subitem" />
@endforeach
</ul>
@endif
</li>
This is the html for single menu item. Every menu item rendered as <li> element. We are checking for $item->children->count, this tells us if there are children for this item. If so then we are iterating using @foreach loop and invoking the same livewire component <livewire:menu-item /> passing the :item="$subitem"prop:
@foreach($item->children as $subitem)
<livewire:menu-item :item="$subitem" />
@endforeach
Styling the menu
Let’s add some styles for our menu to look better, open your layout file and add these styles in the <head> element:
<style>
nav ul {
background: #33e133;
box-shadow: 4px 3px 10px #ccc;
}
nav ul li {
display: inline-block;
padding: 8px 6px;
/*padding-top: 7px;*/
margin-inline-start: 12px;
position: relative;
}
nav ul li > ul {
display: none;
position: absolute;
left: 0;
background: #e04807;
top: 99%;
width: 119px;
}
nav ul li > ul li {
display: block;
margin-inline-start: 0;
}
nav ul li > ul li > ul{
left: 100%;
top: 0;
background: orange;
}
nav ul li.has-children > a:after {
content: '\25BC'
}
nav ul li > ul li.has-children > a:after {
content: '\25BA'
}
nav ul li:hover > ul {
display: block;
}
</style>
To test this create a view file, for example home.blade.php:
resources/views/home.blade.php
<x-layouts.app>
<livewire:navigation-menu />
</x-layouts.app>
In the home view i invoked the navigation-menu livewire component.
Add a route for this view in routes/web.php
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('home');
});
Launch the project and navigate to the home route, you will see the menu.


