Backend Development

Rendering Recursive Components in Laravel Livewire

Rendering Recursive Components in Laravel Livewire

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.

Source Code

0 0 votes
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments