F
Filament•5mo ago
Grégoire

Looping through multiple Livewire components containing Filament Action crashes Firefox/Safari

đź‘‹ Hi Filament builders! Context I'm migrating my action modals from Wire Elements Pro (Modal) to Filament Actions to achieve a more consistent UI and reduce code maintenance. On a search pages, I'm implementing a feature where users can perform actions for each card by opening a dropdown that lazy loads options including an Action. Problem I've discovered that loading a certain number of Livewire components containing Filament action buttons causes the page to crash on both Firefox and Safari. The error messages differ between browsers (see bellow). Chrome appears unaffected by this issue. Removing InteractsWithActions solves the problem (but the action is not usable anymore). Minimum example
@for($i = 0; $i < 40; $i++)
<card>
[...]
<dropdown>
<trigger />
<x-slot:body>
<livewire:test2-submenu lazy>
</x-slot>
</dropdown>
</card>
@endfor
@for($i = 0; $i < 40; $i++)
<card>
[...]
<dropdown>
<trigger />
<x-slot:body>
<livewire:test2-submenu lazy>
</x-slot>
</dropdown>
</card>
@endfor
#[Lazy]
class Test2Submenu extends Component implements HasForms, HasActions
{
use InteractsWithActions, InteractsWithForms;
public function testAction()
{
return Test2Action::make('test');
}
}
#[Lazy]
class Test2Submenu extends Component implements HasForms, HasActions
{
use InteractsWithActions, InteractsWithForms;
public function testAction()
{
return Test2Action::make('test');
}
}
Error messages Firefox (after reloading within 10s) - Too many calls to Location or History APIs within a short timeframe. - Uncaught DOMException: The operation is insecure. Safari - SecurityError: Attempt to use history.replaceState() more than 100 times per 10 seconds Reproducible repo The Firefox issue seems to be related to Livewire itself and has been discussed here, but no solution is currently available. I've created a simple example repository to reproduce the issue. The code is available on GitHub, and you can access the demo on the /test2 route. Question 👉 I may not be using Filament Actions as intended. I'd appreciate your thoughts on this issue and any suggestions for working around it. ---
No description
Solution:
@Grégoire I've found a solution that works even when leaving the InteractsWithActions. This is a temporary solution, allowing you to space out requests if they're too close to each other. Just add this JS script to your page: ```js...
Jump to solution
9 Replies
Grégoire
GrégoireOP•5mo ago
One workaround I've found is to move the logic to the parent component instead of keeping it within the lazy-loaded dropdown content. Then, you can trigger the Filament action with wire:click="$parent.mountAction('test')". This approach ensures the action is only loaded once rather than multiple times. I tested it, and it works well. However, what I don't like about this solution is that the logic is now separated from the dropdown content. This means that every time I use the dropdown, I have to remember to include the action logic in the parent component (hopping there is one). Additionally, if I want to use the dropdown as a standalone component (on a page with just one option), it won't function properly.
Hugo
Hugo•5mo ago
Hi, I have the same issue on my side, I create a task manager where each task is a livewire component. From a certain number of tasks (~50) I have the same issue on Firefox... Error: "Too many calls to the Location or History API in a short period of time." Due to this line in livewire js: window.history.replaceState(state, “”, url.toString());
<ul wire:sortable-group.item-group="group-{{ $group->id }}" wire:sortable-group.options="{ animation: 100 }" class="py-1">
@foreach($tasks->whereNull('parent_id') as $task)
<livewire:task-row :$task :$sortBy :key="$task->id" />
@endforeach
</ul>
<ul wire:sortable-group.item-group="group-{{ $group->id }}" wire:sortable-group.options="{ animation: 100 }" class="py-1">
@foreach($tasks->whereNull('parent_id') as $task)
<livewire:task-row :$task :$sortBy :key="$task->id" />
@endforeach
</ul>
No description
Hugo
Hugo•5mo ago
And indeed I confirm that by removing the InteractsWithActions I no longer have the problem, however this is really problematic for the logic of my application...
Grégoire
GrégoireOP•5mo ago
Same on my end. I failed to find a solution and went back to dynamic modals with wire elements and setting up a form with inside a classic Livewire component. Not great because that means maintaining two logics at the same time.
Hugo
Hugo•5mo ago
Have you been able to create an issue on filament or livewire ?
Grégoire
GrégoireOP•5mo ago
There are some discussions on #7746, but it has been going on for a while.
GitHub
Too many calls to location or history APIs in a short period of tim...
Livewire version 3.3.5 Laravel version 10.41.0 Which PHP version are you using? PHP 8.3 Steps To Reproduce Using Firefox, when you click several times on one or more links in the side menu (I'm...
Hugo
Hugo•5mo ago
Yes, I came across it while looking for a solution. I hope this problem will be solved
Solution
Hugo
Hugo•5mo ago
@Grégoire I've found a solution that works even when leaving the InteractsWithActions. This is a temporary solution, allowing you to space out requests if they're too close to each other. Just add this JS script to your page:
@push('scripts')
<script>
const original = window.history.replaceState;
let timer = Date.now();

let timeout = null;
let lastArgs = null;

window.history.replaceState = function (...args) {
const time = Date.now();
if (time - timer < 300) {
lastArgs = args;

if (timeout) {
clearTimeout(timeout);
}

timeout = setTimeout(() => {
original.apply(this, lastArgs);

timeout = null;
lastArgs = null;
}, 100);

return;
}

timer = time;

original.apply(this, args);
};
</script>
@endpush
@push('scripts')
<script>
const original = window.history.replaceState;
let timer = Date.now();

let timeout = null;
let lastArgs = null;

window.history.replaceState = function (...args) {
const time = Date.now();
if (time - timer < 300) {
lastArgs = args;

if (timeout) {
clearTimeout(timeout);
}

timeout = setTimeout(() => {
original.apply(this, lastArgs);

timeout = null;
lastArgs = null;
}, 100);

return;
}

timer = time;

original.apply(this, args);
};
</script>
@endpush
Grégoire
GrégoireOP•4mo ago
Nice solution @Hugo Myb. I have reverted back to Wire Elements so I am not using your fix for the moment, but I will definitely keep it in mind!
Want results from more Discord servers?
Add your server