livewire: Select element with pre-selected value not rendering correctly

Describe the bug: A select element with a pre selected value does not render correctly for the user

To Reproduce

  1. Create a blade template with a select element
  2. Pass a value from livewire to the blade template
  3. Use standard blade syntax to set the pre-selected value

Expected result: If the second option in the select element is selected then this should be visible to the user when the dom renders.

Actual result: Always the first option in the select element is visible to the user even though another option has the correct html selected tag.

Blade Input:

<select id="role_id" wire:model="role_id">
    <option value="1" {{ $role_id == 1 ? 'selected="selected"' : '' }}>Owner</option>
    <option value="2" {{ $role_id == 2 ? 'selected="selected"' : '' }}>Collaborator</option>
</select>

HTML Output

<select id="role_id" wire:model="role_id">
    <option value="1">Owner</option> 
    <option value="2" selected="selected">Collaborator</option>
</select>

Screenshots image

Desktop (please complete the following information):

  • Browser: chrome

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 6
  • Comments: 38 (3 by maintainers)

Most upvoted comments

Ok I figured this. to anybody who is still looking.

<select wire:model="status">
                        <option value="1">  Approved </option>
                        <option value="2">  Un-Approved </option>
</select>

That’s all you have to do. But initially it won’t be having any value, so you have to set value in mount method like this

public function mount(){
   $this->status = 1;
}

and then It will be automatically selected. You can do this in render method too incase you want to set every time component refreshes.

It’s still isn’t fixed.

Here’s the solution in case anyone else is beating their head to the table:

Just don’t refresh the select and no need to add “selected” Basically wrap your select with wire:ignore

<div wire:ignore> <select wire:model="mode-name-here"> <option>1</option> <option>2</option> <option>3</option> </select> </div>

Ok, just fixed it and it was quite trivial. My mistake… Setting the wire:key for every item fixed the behavior. The $qp variable contains the query parameters.

<select name="perPage"
              wire:model="perPage"
              class="form-control"
              id="perPage">{{$perPage}}
                    <option wire:key="10" @if($qp['perPage'] === 10) selected @endif>10</option>
                    <option wire:key="15" @if($qp['perPage'] === 15) selected @endif>15</option>
                    <option wire:key="25" @if($qp['perPage'] === 25) selected @endif>25</option>
</select>

@LewisLarsen I use that without issue <option value="">Select a category</option> (make sure to remove the selected) and set your wire:model item to ‘’ in your component and have it validate to an integer if you want to force a choice

alternatively you can set the default value to -1 <option value="-1">Select a category</option>

validate you item to ‘required|numeric|min:1’ or ‘digits_between:1,x’ for example if you have X elements and each element is increasing in value. go with the first one 😃

Seeing as this thread comes from 2019, and Livewire has been updated 2 major versions, it’s surprising to see something like this still be an issue. However, reading all these comments, and testing some things, I think it’s clear that this will never be fixed, as this is against how Livewire is functioning.

Let me explain: The Livewire component passes data to a view, and renders it using this data. Because of this, values get manipulated after the HTML is prepared. A simple test can be done with an input. When you give this input a value, and a wire:model linked to a variable with no value, the input is empty. This ties in to what @calebporzio has said about the select:

Also, Livewire intelligently sets selected states, so you can get rid of the manually echoing out selected="selected"

When the linked variable has a value that is in the select, it will automatically be selected on the first render. But, a lot of people forget this, as it doesn’t feel natural. I hit this wall as well. Because HTML automatically sets the first option to render as selected, it feels like it should be selected. However, Livewire has not gotten a value for this select yet. Therefore, on instant submit, it is still empty.

The solution is however relatively simple, and all one has to do is set the value in the component, either the variable itself, or in the mount function, what @jiteshdhamaniya already mentioned:

That’s all you have to do. But initially it won’t be having any value, so you have to set value in mount method like this

public function mount(){
   $this->status = 1;
}

and then It will be automatically selected. You can do this in render method too incase you want to set every time component refreshes.

I feel like this issue could simply be closed by giving a hint to this behaviour on the docs.

Also, Livewire intelligently sets selected states, so you can get rid of the manually echoing out selected="selected"

How and where? I could find nothing in the documentation.

@evergreenwebsystems - this should be fixed in the latest release.

I think the problem is that $role_id needs to be a string, and you were setting it to a numeric value. I fixed livewire to handle strings to set the value of a select input.

This should be fixed in the latest release (will probably be in v0.1.2)

Also, Livewire intelligently sets selected states, so you can get rid of the manually echoing out selected="selected"

to all who is Still facing this issue use wire:change instead of wire:model this fixed my problem

            <select id="categoryID" wire:change="categoryID">
                <option @empty($data) selected="selected" @endempty>---------------</option>
                @foreach ($categoryModel->where("is_deleted",false) as $category)
                <option @isset($data) {{ $data->category_id == $category->id ? "selected":"" }}@endisset
                    value="{{ $category->id }}">{{ $category->name }} </option>
                @endforeach
            </select>

Adding a random $this->status = 1 means absolutely nothing without context. Sticking that in my code did nothing, as expected. There needs to be a more clear and thorough explanation of how Livewire renders the select menu, including working code examples.

@iforaberepo I understand that it’s still not quite clear. Livewire is, after all, quite a jump from your normal use case in Laravel. I’ll give you some code samples:

  1. Example in which the select is NOT set:

Livewire component:

<?php

namespace App\Http\Livewire\Users;

use Livewire\Component;

class UpdateProfile extends Component
{
    public $statuses = [
        0 => 'active',
        1 => 'pending',
        2 => 'inactive',
    ];

    public $status;
    
    public function render()
    {
        return view('livewire.users.update-profile');
    }

     public function submit()
    {
        dd($this->status);
    }
}

View:

<div>
    <form class="space-y-8" wire:submit.prevent="submit">
        <select wire:model="status">
            @foreach($statuses as $index => $selectStatus)
                <option value="{{ $index }}">{{ $selectStatus }}</option>
            @endforeach
        </select>

        <button type="submit">
            Submit
        </button
    </form>
</div

Explanation: When opening this view, and immediately clicking the submit button, the output for this code block will be null. This is because in Livewire, the value currently set for $status is null. However, because HTML by default displays the first option in a select, it looks like an item is selected.

  1. Making it set:

There are 2 suggested ways of ensuring the value of $status:

  • Option 1; the mount function:
public function mount(){
    $this->status = 1;
}

The mount function, as described under the tag ‘Receiving Parameters’, is the __construct method for Livewire components.

When adding this function (I usually place it right above the render function), you can do things to your own wishes before the component is first initialized. By setting $status in the mount function, it will contain a value, and so when then immediately pressing the submit button, the output will be 1; In my most use cases, the mount function is great for pre-filling variables from a model. This can easily be done using $this->fill($model->getAttributes());.

  • Option 2; default value:

Instead of using the mount function, you can also simply set a default value when setting the public parameter. public $status = 1; will also give the same result.

When playing around with these values, you will see that the select will change value upon first render, even though you haven’t touched it. The same goes for inputs, checkboxes, textareas and such. When the wire:model is linked, Livewire handles all the necessary steps to fill the fields with data.

I hope this clears things up a bit more!

P.S. When typehinting these attributes, an exception will be thrown if the value is not type hinted as nullable (e.g. ?string) when trying to access it without value.

I think the problem is that $role_id needs to be a string, and you were setting it to a numeric value. I fixed livewire to handle strings to set the value of a select input.

I changed $id to string. 'selected' still doesn’t work for my.

my code:

<select wire:model="permissions"  class="form-select mt-1 block w-full @error('permissions') is-invalid @enderror "  id="rolePermissions" name="rolePermissions" multiple>
    @foreach ($permissionsList as $permission)
         <option wire:key="{{ $loop->index }}" value="{{ $permission->id }}"
             @foreach ($role->getAllPermissions() as $rolePermission)
                  {{ strval($permission->id) == strval($rolePermission->id) ? 'selected' : '' }}
             @endforeach
         >
                {{ $permission->name }}
          </option>
    @endforeach
</select>

Screenshot

to all who is Still facing this issue use wire:change instead of wire:model this fixed my problem

            <select id="categoryID" wire:change="categoryID">
                <option @empty($data) selected="selected" @endempty>---------------</option>
                @foreach ($categoryModel->where("is_deleted",false) as $category)
                <option @isset($data) {{ $data->category_id == $category->id ? "selected":"" }}@endisset
                    value="{{ $category->id }}">{{ $category->name }} </option>
                @endforeach
            </select>

This is the only solution that seems to work for me. This is still broken.

Setting $role_id as a string does indeed fix the issue. Amazing that it also intelligently sets the selected state.

Thanks!