I am using a ui-select field in my Magento 2 admin form. The dropdown fetches data via AJAX on search and correctly displays options. However, I'm facing an issue:
First search: Options load correctly, and I can select one without any problems.
After clearing the search and searching again: The new options appear correctly, but when I select an option, the field displays:
"Entity with ID: ... doesn't exist"
After this issue appears, any further selection also shows the same message instead of displaying the correct label.
There are no console errors. The selected value is correctly saved and retrieved, but the label does not display properly.
Upon debugging, I found that the issue originates from Magento_Ui/js/form/element/ui-select, particularly in the setCaption function:
setCaption: function () { var length, caption = ''; if (!_.isArray(this.value()) && this.value()) { length = 1; } else if (this.value()) { length = this.value().length; } else { this.value([]); length = 0; } this.warn(caption); // Check if the option was removed if (this.isDisplayMissingValuePlaceholder && length && !this.getSelected().length) { caption = this.missingValuePlaceholder.replace('%s', this.value()); this.placeholder(caption); this.warn(caption); return this.placeholder(); } if (length > 1) { this.placeholder(length + ' ' + this.selectedPlaceholders.lotPlaceholders); } else if (length && this.getSelected().length) { this.placeholder(this.getSelected()[0].label); } else { this.placeholder(this.selectedPlaceholders.defaultPlaceholder); } return this.placeholder(); },
When the label displays correctly:
<div class="admin__action-multiselect-text" data-role="selected-option" data-bind="css: {warning: warn().length}, text: setCaption()">Five Guys OCS</div>
When the issue occurs:
<div class="admin__action-multiselect-text warning" data-role="selected-option" data-bind="css: {warning: warn().length}, text: setCaption()">Entity with ID: 13166 doesn't exist</div>
<field name="list_id"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="label" xsi:type="string" translate="true">List Name</item> <item name="componentType" xsi:type="string">field</item> <item name="formElement" xsi:type="string">select</item> <item name="component" xsi:type="string">Vendor_Module/js/form/element/orderlist-select</item> <item name="elementTmpl" xsi:type="string">ui/grid/filters/elements/ui-select</item> <item name="dataUrl" xsi:type="url" path="orderlists/orderlist/search"/> <item name="searchUrlParameter" xsi:type="string">searchTerm</item> <item name="hasSearchInput" xsi:type="boolean">true</item> <item name="filterOptions" xsi:type="boolean">true</item> <item name="lazyLoad" xsi:type="boolean">true</item> <item name="selectType" xsi:type="string">single</item> <item name="multiple" xsi:type="boolean">false</item> <item name="required" xsi:type="boolean">true</item> <item name="dataScope" xsi:type="string">list_id</item> </item> </argument> </field>
define([ 'Magento_Ui/js/form/element/ui-select', 'jquery', 'ko' ], function (UiSelect, $, ko) { 'use strict'; return UiSelect.extend({ defaults: { isLoading: ko.observable(false), options: ko.observableArray([]), showFilteredQuantity: true }, initialize() { this._super(); this._initSearchListener(); // If there's an existing value, fetch its label if (this.value()) { this.loadInitialLabel(this.value()); } return this; }, _initSearchListener() { this.filterInputValue.subscribe(searchTerm => { if (searchTerm.length >= 1) { this.fetchData(searchTerm); } }); }, fetchData(searchTerm) { if (!this.dataUrl) return; this.isLoading(true); $.ajax({ url: this.dataUrl, method: 'GET', dataType: 'json', data: { searchTerm } }) .done(response => { if (Array.isArray(response)) { this.options([]); // Clear previous options this.options(response); // Load new options this.hasData(true); this.trigger('optionsUpdated'); // Refresh UI } else { console.error('Invalid response format:', response); } }) .fail(xhr => console.error('fetchData error:', xhr)) .always(() => this.isLoading(false)); }, loadInitialLabel(listId) { let self = this; $.ajax({ url: this.dataUrl, // Use same endpoint to get the label method: 'GET', dataType: 'json', data: { listId } }).done(response => { if (response && response.label) { self.options([{ value: listId, label: response.label }]); self.value(listId); } }).fail(xhr => console.error('loadInitialLabel error:', xhr)); }, getPath() { return ''; } }); });
<?php namespace Vendor\Module\Controller\Adminhtml\OrderList; use Magento\Backend\App\Action; use Magento\Framework\Controller\Result\JsonFactory; use Vendor\Module\Model\ResourceModel\OrderList\CollectionFactory as OrderListCollectionFactory; class Search extends Action { protected $orderListCollectionFactory; protected $jsonFactory; public function __construct( Action\Context $context, OrderListCollectionFactory $orderListCollectionFactory, JsonFactory $jsonFactory ) { parent::__construct($context); $this->orderListCollectionFactory = $orderListCollectionFactory; $this->jsonFactory = $jsonFactory; } public function execute() { $searchTerm = trim($this->getRequest()->getParam('searchTerm', '')); $listId = trim($this->getRequest()->getParam('listId', '')); $collection = $this->orderListCollectionFactory->create(); if ($listId) { $item = $collection->addFieldToFilter('list_id', $listId)->getFirstItem(); if ($item->getId()) { return $this->jsonFactory->create()->setData([ 'value' => (string) $item->getListId(), 'label' => $item->getName() ]); } return $this->jsonFactory->create()->setData([]); } if (!$searchTerm) { return $this->jsonFactory->create()->setData([]); } $collection->addFieldToFilter('name', ['like' => "%{$searchTerm}%"])->setPageSize(50); $options = []; foreach ($collection as $item) { $options[] = [ 'value' => (string) $item->getListId(), 'label' => $item->getName() ]; } return $this->jsonFactory->create()->setData($options); } }
How can I ensure that the selected option always displays its correct label instead of "Entity with ID ... doesn't exist" when searching again? What might be causing getSelected() to return an empty value after a new search?
Hello @Partab Saif,
The issue you're facing is likely due to the fact that the options array is being overwritten when new search results are loaded, which causes the previously selected option to be lost if it's not part of the new search results. This results in the getSelected() method returning an empty array, leading to the "Entity with ID ... doesn't exist" error.
To fix this issue, you need to preserve the currently selected option when fetching new options. You can achieve this by checking if the current value is still valid and merging it with the new search results.
Update the fetchData method to retain the currently selected option:
fetchData(searchTerm) { if (!this.dataUrl) return; this.isLoading(true); const currentValue = this.value(); $.ajax({ url: this.dataUrl, method: 'GET', dataType: 'json', data: { searchTerm } }) .done(response => { if (Array.isArray(response)) { const updatedOptions = []; // If there is a selected value, ensure it remains in the options if (currentValue) { const existingOption = this.getSelected()[0]; if (existingOption) { updatedOptions.push(existingOption); } else { // Attempt to load the label for the current value if missing this.loadInitialLabel(currentValue); } } // Add new search results to the options updatedOptions.push(...response); this.options(updatedOptions); // Load new options this.hasData(true); this.trigger('optionsUpdated'); // Refresh UI } else { console.error('Invalid response format:', response); } }) .fail(xhr => console.error('fetchData error:', xhr)) .always(() => this.isLoading(false)); },
Make sure loadInitialLabel is called only when needed and is able to update the options properly:
loadInitialLabel(listId) { let self = this; $.ajax({ url: this.dataUrl, // Use same endpoint to get the label method: 'GET', dataType: 'json', data: { listId } }).done(response => { if (response && response.label) { const existingOptions = self.options(); existingOptions.push({ value: listId, label: response.label }); self.options(existingOptions); // Preserve previous options self.value(listId); } }).fail(xhr => console.error('loadInitialLabel error:', xhr)); }