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.