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

vincent15000's avatar

Nested forms to build a questionnaire

Hello,

I have this problem in a Laravel/Livewire application (Livewire v3).

The user needs to build a questionnaire which looks like this.

a questionnaire has a name and belongs to a training

a questionnaire has many groups of questions, a group of questions has a name

a group of questions has many questions, a question has a name and a question type

I have 3 tables : questionnaires, question_groups and questions.

I want that the user can type all his questions in a unique form to add / remove groups and questions.

I mean here a Livewire Form component like proposed since Livewire version 3.

To have this working, is it better to handle the questionnaire, the groups and the questions in one unique Livewire form ? or is it better to have 3 Livewire forms ?

When I write the validation rules, I have something like this.

#[Validate('nullable', onUpdate: false)]
public $id = null;

#[Validate('required', onUpdate: false)]
public $name = '';

public $groups = []; // here it's an array of forms

I don't have any idea how to organize my code to do that.

Can you suggest me some ideas ?

Thanks ;).

V

0 likes
7 replies
vincent15000's avatar

@jlrdw I'm really lost on how to write my code.

Here is what I have tried to do (some extracts of my code)

// questionnaire form


public ?Questionnaire $questionnaire;

#[Validate('nullable', onUpdate: false)]
public $id = null;

#[Validate('required', onUpdate: false)]
public $name = '';

#[Validate('required', onUpdate: false)]
public $groups = [];

public function set(Questionnaire $questionnaire)
{
    $this->id = $questionnaire->id;
    $this->name = $questionnaire->name;
    $this->groups = $questionnaire->groups;
}

// questionnaire form page controller

public QuestionnaireForm $form;

public function mount(Questionnaire $questionnaire)
{
    $this->form->set($questionnaire->load('groups.questions'));
}

// questionnaire form page

<form class="flex flex-col gap-4" wire:submit="save()">
    <x-form.input type="text" wire:model="form.name">Nom</x-form.input>

    @foreach ($form->groups as $group)
        <livewire:questionnaires.question-group-form-component :$group></livewire:questionnaires.question-group-form-component>
    @endforeach
</form>

// group form

public ?QuestionGroup $group;

#[Validate('nullable', onUpdate: false)]
public $id = null;

#[Validate('required', onUpdate: false)]
public $name = '';

#[Validate('required', onUpdate: false)]
public $questions = [];

// group form component controller

public QuestionGroupForm $form;
public QuestionGroup $group;
public $index;

public function mount()
{
    $this->form->set($this->group->load('questions'));
}

// group form component (blade)

<div>
    <div class="space-y-2">
        <x-form.input
            class="font-bold"
            type="text"
            wire.model="form.name"
        >
            Groupe {{ $index }}
            <x-link-button
                class="ml-2 text-red-500 hover:text-red-600 text-sm"
                type="button"
                wire:click="removeGroup()"
            >
                Supprimer
            </x-link-button>
        </x-form.input>
    </div>
</div>

It's not working, I don't see the values inside the input fields.

What am I doing wrong ?

Can you help me please ?

I'm searching for the solution for some hours now and I really don't see what I can do.

Thanks a lot.

jlrdw's avatar

@vincent15000 in html 5, I don't think you can nest form elements. That's why I suggested something like an accordion.

Or you can have hidden div's you show to fill out, hide div-1, show div-2 fill, etc. That way a single form.

Or if multiple forms, you can use session, or pass an array or object holding the data.

I'd use regular javascript and ajax. But I haven't dwelt with livewire since the first version.

Surely there's a way to use some JS with the latest livewire.

Maybe try the divisions with livewire first to see how they will work.

WIth regular JS and fetch js, it's easy.

You have a div with display of none and when needed, you just

document.getElementById("myModal").style.display = "block";

But just example.

1 like
vincent15000's avatar

@jlrdw It's not really nested form tags, but pieces of form (fields) nested inside another piece of form (fields) inside a form. But I understand what you mean.

It's perhaps a better idea for me to follow another solution than the one I hoped to be working.

Perhaps AlpineJS is a good idea too.

vincent15000's avatar

@Snapey The button isn't inside the input element, it is inside the label. It was provisional, just for testing something, but I know I have to put it outside the label.

Have a look at the input component.

<x-form.field-wrapper>
    <x-form.label>{{ $slot }}</x-form.label>
    <input {{ $attributes->merge(['class' => 'outline-primary-500 border border-primary-500 rounded-lg px-3 py-2']) }}>
</x-form.field-wrapper>
vincent15000's avatar

@jlrdw @snapey I have solved my problem by using AlpineJS and @entangle to bind the variables to those in the controller and it works fine. Probably the best solution.

Please or to participate in this conversation.