Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

pdigital's avatar

Load HasMany relation in Filament Repeater in Step Wizard

I'm new to FilamentPHP and Livewire and I am creating a reservation application. Consider the following model:

class Attendee extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $fillable = [
        'attendee_id', // nullable
        'relation_id',
        'save_the_date_sent_at',
        'rsvp_sent_at',
        'rsvp_reminder_sent_at',
        'responded_at',
        'name',
        'email',
        'phone_number',
        'rsvp_day',
        'rsvp_evening',
        'language',
        'residence',
        'type',
        'notes',
    ];

    public function childAttendees(): HasMany
    {
        return $this->hasMany(Attendee::class, 'attendee_id', 'id');
    }

    public function mainAttendee(): BelongsTo
    {
        return $this->belongsTo(Attendee::class, 'attendee_id');
    }
}

I have an action in a Livewire component, which loads a step wizard form.

In step 1, the user types in a name. Filament (or Livewire, not sure who's responsible?) should retrieve the Attendee model. If it's found, then the user should be able to click the next button. If not, the next button should be disabled with some type of 'Attendee not found' message.

When the Attendee is found, I would like Step 2 to show the names of all attendees linked to the main attendee, with a simple checkbox next to each name, with the question: "Will person x also attend" ?

If the Attendee found in step 1 is NOT a main attendee but a child attendee, it should load all childattendees using the main attendee:

$attendee->mainAttendee->childAttendees;

So far with reading the Filament documentation, I can retrieve the Attendee in step 1 and set it. But I'm not sure which way to go after that. Any pointers? (It does not need to be a Repeater by the way) My code:

class Home extends Component implements HasForms, HasActions
{
    use InteractsWithActions;
    use InteractsWithForms;

    public ?Attendee $attendee = null;

    public function rsvpAction(): Action
    {
        return Action::make('rsvp')
            ->label('RSVP')
            ->slideOver()
            ->steps([
                Step::make('Name')
                    ->description('What is your name?')
                    ->schema([
                        TextInput::make('name')
                            ->required()
                            ->afterStateUpdated(function ($state, Set $set) {
                                $attendee = Attendee::where('name', $state)->first();

                                $this->attendee = $attendee; // not sure to use $set or to save it in the public Attendee property?
                                $set('attendee', $attendee);
                            })
                    ])
                    ->afterValidation(function (Get $get) {
                        $attendee = $get('attendee');

                        if (!$attendee) {
                            throw new Halt();
                        }

                        \Log::info($attendee);
                    }),
                Step::make('Attendance')
                    ->description('Will you be attending?')
                    ->schema([
                        Checkbox::make('Will you be attending?')
                            ->required(),
                        Repeater::make('childAttendees') // now throws Call to a member function childAttendees() on null
                            ->relationship()
                            ->schema([
                                Checkbox::make('attending')
                            ]),
                    ])
            ])
            ->action(function (array $data) {
                info(print_r($data, true));
            });
    }

    public function render(): View
    {
        return view('livewire.home');
    }
}
0 likes
2 replies
LaryAI's avatar
Level 58

To achieve the desired functionality, you'll need to make a few adjustments to your Livewire component. Here's a step-by-step guide to modify your rsvpAction method to handle the Attendee lookup and display the child attendees in the second step of the wizard.

  1. Modify the afterStateUpdated callback to set the attendee property correctly.
  2. Use a conditional to determine if the attendee is a main attendee or a child attendee and load the appropriate child attendees.
  3. Update the schema for the Attendance step to dynamically generate checkboxes for each child attendee.

Here's how you can adjust your code:

class Home extends Component implements HasForms, HasActions
{
    use InteractsWithActions;
    use InteractsWithForms;

    public ?Attendee $attendee = null;
    public $childAttendees = [];

    public function rsvpAction(): Action
    {
        return Action::make('rsvp')
            ->label('RSVP')
            ->slideOver()
            ->steps([
                Step::make('Name')
                    ->description('What is your name?')
                    ->schema([
                        TextInput::make('name')
                            ->required()
                            ->afterStateUpdated(function ($state) {
                                $this->attendee = Attendee::where('name', $state)->first();
                                if ($this->attendee) {
                                    // Determine if the attendee is a main attendee or a child attendee
                                    $mainAttendee = $this->attendee->attendee_id ? $this->attendee->mainAttendee : $this->attendee;
                                    // Load the child attendees
                                    $this->childAttendees = $mainAttendee->childAttendees;
                                }
                            })
                    ])
                    ->afterValidation(function (Get $get) {
                        if (!$this->attendee) {
                            throw new Halt();
                        }
                    }),
                Step::make('Attendance')
                    ->description('Will you be attending?')
                    ->schema([
                        Checkbox::make('Will you be attending?')
                            ->required(),
                        // Dynamically generate checkboxes for each child attendee
                        ...$this->getChildAttendeeSchema(),
                    ])
            ])
            ->action(function (array $data) {
                info(print_r($data, true));
            });
    }

    // Helper method to generate the schema for child attendees
    private function getChildAttendeeSchema()
    {
        return collect($this->childAttendees)->map(function ($childAttendee) {
            return Checkbox::make("childAttendees.{$childAttendee->id}.attending")
                ->label("Will {$childAttendee->name} also attend?");
        })->toArray();
    }

    public function render(): View
    {
        return view('livewire.home');
    }
}

In this solution, we've added a new property $childAttendees to store the child attendees. We've also created a helper method getChildAttendeeSchema to dynamically generate the schema for the child attendees based on the loaded $childAttendees property.

Please note that this code assumes that you have a view file livewire.home that is properly set up to render the Livewire component. Also, ensure that your Livewire and Filament versions are up to date to avoid any compatibility issues with the provided code.

pdigital's avatar

@LaryAI Nice. Got this to work mostly. However, the child attendees in step 2 is not immediately shown after I click "next". It only works after the second time I click next.

It seems that the Step 2 is being loaded before it has the child attendees.

Please or to participate in this conversation.