foreach() argument must be of type array | object, null given (Laravel Livewire)
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Mastering Livewire Pagination: Why Your $products->links() Call Fails
As developers working with dynamic interfaces like Laravel Livewire, handling data retrieval and presentation efficiently is key. One of the most common stumbling blocks arises when integrating Eloquent pagination with Livewire components. You might see code that works perfectly for simple data fetching, but when you introduce complex objects like pagination results, unexpected errors crop up.
This post dives into a specific issue: why attempting to use $products->links() inside a Livewire component's render() method after paginating your data can lead to errors, and how to structure your code correctly.
The Paradox of Pagination in Livewire
Let’s examine the scenario you presented. You are trying to display products with pagination links within a Livewire component:
Working Scenario (Simple Data):
public function render(){
$this->products = ProductModel::get(); // Returns an array or collection
return view('livewire.product');
}
This works fine because $this->products holds a simple collection of models, which Blade handles seamlessly.
Failing Scenario (With Pagination):
public function render(){
return view('livewire.product', [
'products' => ProductModel::paginate(10) // Returns a LengthAwarePaginator object
]);
}
When you use the paginate() method on an Eloquent query, it does not return a simple array; it returns a LengthAwarePaginator object. This object contains metadata about the current page, total items, and crucially, the pagination links themselves (accessible via $products->links()).
The error message you are encountering—something like foreach() argument must be of type array | object, null given—occurs because Livewire or Blade is expecting a simple iterable structure for the main data set, but when it encounters the paginator object, the internal rendering logic doesn't know how to interpret that complex object directly in the context of a standard loop setup.
The Correct Approach: Separating Data and Links
The solution lies in understanding that you need to separate the primary data (the products) from the navigation elements (the links). While it is possible to put both into the view, the most robust pattern is often to ensure the data structure passed to the view is clean.
For Livewire components utilizing pagination, we should pass the paginator object directly, but we must ensure the Blade file correctly handles the properties of that object. The error often stems from trying to access methods on a non-array structure in unexpected places.
Implementing the Fix in Your View
The correct way to handle this is to trust that Eloquent's paginator object contains exactly what you need for iteration and navigation, and ensure your Blade file accesses these properties correctly.
Controller/Component Method (No change needed here):
public function render(){
return view('livewire.product', [
'products' => ProductModel::paginate(10) // Keep the paginator object
]);
}
Blade File (livewire/product.blade.php):
The structure you have is actually very close to correct, but we need to ensure the logic around $products->links() is sound when dealing with Livewire's lifecycle.
@foreach ($products as $product)
<div>
{{ $product->name }} - ${{ $product->price }}
</div>
@endforeach
{{-- This line correctly accesses the pagination links provided by the paginator object --}}
@if ($products->has('links'))
<div class="pagination-links">
{{ $products->links() }}
</div>
@endif
Notice how we explicitly check for the existence of links. While $products->links() often works, checking properties or using methods provided by the paginator ensures robustness across different Laravel versions and Livewire contexts. If you are working heavily with Eloquent relationships and data fetching, sticking to solid patterns, as promoted by resources like those found on laravelcompany.com, will prevent these kinds of subtle rendering errors down the line.
Conclusion
The error you encountered is a classic example of a mismatch between the data type expected by the rendering engine (Blade/Livewire) and the complex object returned by Eloquent's pagination methods. By understanding that pagination returns a Paginator object rather than a simple array, we can adjust our rendering logic to correctly access both the item data (foreach) and the navigation links (links()). Always treat Eloquent results as structured objects when dealing with complexity like pagination, ensuring your Livewire components remain clean, predictable, and error-free.