Visual Inconsistencies in Filament Repeater with Dynamic Select Options

We are using a Filament form with a Repeater that contains a Select field to choose workers from the User model, filtered by the Employee role. The requirement is that once a worker is selected in one row, they should not appear in the Select options of other rows.
Select::make('worker_id')->reactive()
->options(function (callable $get) {
$allUsers = User::role('Employee')->pluck('name', 'id');
$selectedUsers = collect($get('../../taskGroups'))->pluck('worker_id')->filter()->toArray();

return $allUsers->filter(function ($name, $id) use ($selectedUsers) {
return !in_array($id, $selectedUsers);
});
})
->rules([
function ($component) {
return function (string $attribute, $value, Closure $fail) use ($component) {
$items = $component->getContainer()->getParentComponent()->getState();
$selected = array_column($items, $component->getName());

if (count(array_unique($selected)) < count($selected)) {
$fail('A worker can only be selected once.');
}
};
},
])
Select::make('worker_id')->reactive()
->options(function (callable $get) {
$allUsers = User::role('Employee')->pluck('name', 'id');
$selectedUsers = collect($get('../../taskGroups'))->pluck('worker_id')->filter()->toArray();

return $allUsers->filter(function ($name, $id) use ($selectedUsers) {
return !in_array($id, $selectedUsers);
});
})
->rules([
function ($component) {
return function (string $attribute, $value, Closure $fail) use ($component) {
$items = $component->getContainer()->getParentComponent()->getState();
$selected = array_column($items, $component->getName());

if (count(array_unique($selected)) < count($selected)) {
$fail('A worker can only be selected once.');
}
};
},
])
Issue: When selecting a worker, the selected worker's name visually jumps to the next available ID after about half a second. However, the originally selected worker does disappear from the list, indicating that the selection is correctly registered but not correctly displayed. Due to the character limit, I will share another thing I tried in the comments Question: Am I doing something wrong, or is this a bug in the Filament framework? Any assistance or suggestions on how to handle this scenario correctly would be greatly appreciated.
1 Reply
Tommika79
Tommika792mo ago
Select::make('worker_id')->columnSpan(1)->reactive()
->relationship('worker', 'name', fn (Builder $query) => $query->whereHas('roles', function (Builder $query) {
$query->whereIn('name', ['Employee', 'Admin']);
}))
->options(function (callable $get, $state) {
$allUsers = User::role(['Employee', 'Admin'])->pluck('name', 'id');
$selectedUsers = collect($get('../../taskGroups'))->pluck('worker_id')->filter()->toArray();

$availableUsers = $allUsers->toArray();
foreach ($selectedUsers as $userId) {
if (isset($allUsers[$userId]) && $state !== $userId) {
unset($availableUsers[$userId]);
}
}

if ($state && isset($allUsers[$state])) {
$availableUsers[$state] = $allUsers[$state];
}

return $availableUsers;
})
->afterStateUpdated(function ($state, callable $set, callable $get) {
$selectedUsers = collect($get('../../taskGroups'))->pluck('worker_id')->filter()->toArray();
if (!in_array($state, $selectedUsers)) {
$set('worker_id', $state);
}
})
->afterStateHydrated(function ($state, callable $set, callable $get) {
$selectedUsers = collect($get('../../taskGroups'))->pluck('worker_id')->filter()->toArray();
if (!in_array($state, $selectedUsers)) {
$set('worker_id', $state);
}
}),
Select::make('worker_id')->columnSpan(1)->reactive()
->relationship('worker', 'name', fn (Builder $query) => $query->whereHas('roles', function (Builder $query) {
$query->whereIn('name', ['Employee', 'Admin']);
}))
->options(function (callable $get, $state) {
$allUsers = User::role(['Employee', 'Admin'])->pluck('name', 'id');
$selectedUsers = collect($get('../../taskGroups'))->pluck('worker_id')->filter()->toArray();

$availableUsers = $allUsers->toArray();
foreach ($selectedUsers as $userId) {
if (isset($allUsers[$userId]) && $state !== $userId) {
unset($availableUsers[$userId]);
}
}

if ($state && isset($allUsers[$state])) {
$availableUsers[$state] = $allUsers[$state];
}

return $availableUsers;
})
->afterStateUpdated(function ($state, callable $set, callable $get) {
$selectedUsers = collect($get('../../taskGroups'))->pluck('worker_id')->filter()->toArray();
if (!in_array($state, $selectedUsers)) {
$set('worker_id', $state);
}
})
->afterStateHydrated(function ($state, callable $set, callable $get) {
$selectedUsers = collect($get('../../taskGroups'))->pluck('worker_id')->filter()->toArray();
if (!in_array($state, $selectedUsers)) {
$set('worker_id', $state);
}
}),
Issue: This code resolves the initial jumping issue but introduces another problem: changing the first worker to another (especially if their ID is one higher than the last selected) causes all rows below it to display the changed worker’s name. Adding or removing a row triggers a refresh that corrects the names, but this is confusing and undesired. Summary of Approaches Tried: Direct Filtering in options Callback:
->options(function (callable $get) {
$allUsers = User::role('Employee')->pluck('name', 'id');
$selectedUsers = collect($get('../../taskGroups'))->pluck('worker_id')->filter()->toArray();

return $allUsers->filter(function ($name, $id) use ($selectedUsers) {
return !in_array($id, $selectedUsers);
});
})
->options(function (callable $get) {
$allUsers = User::role('Employee')->pluck('name', 'id');
$selectedUsers = collect($get('../../taskGroups'))->pluck('worker_id')->filter()->toArray();

return $allUsers->filter(function ($name, $id) use ($selectedUsers) {
return !in_array($id, $selectedUsers);
});
})
Issue: Selected worker names visually jumped to the next available ID. Real-Time Updates with afterStateUpdated:
->afterStateUpdated(function ($state, callable $set, callable $get) {
$set('worker_id', $get('worker_id'));
})
->afterStateUpdated(function ($state, callable $set, callable $get) {
$set('worker_id', $get('worker_id'));
})
Issue: Introduced extra rows and visual inconsistencies. Combining with live Option for Real-Time Changes:
->live()
->live()
Issue: Multiple re-renders led to flickering and incorrect visual updates.
Want results from more Discord servers?
Add your server