One thing you can do to get the data, is to create a Virtual Layer, using SQL count(), st_contains() and group_by
Starting from a Grid and a source_layer with a 'category' field:

Create a Virtual Layer with the following definition:
select Grid.id as grid_id, source_layer.category, count(*) as count, Grid.geometry
from Grid,source_layer
where st_contains(Grid.geometry,source_layer.geometry)
group by Grid.id,source_layer.category

Resulting virtual layer with geometries, attributes and count:
The resulting Attribute Table is the following:
with the grid_id, category, count along with the grid.geometry
There are as many lines per grid item than there are unique categories present in that grid item.
Using the virtual layer to display Labels in the original Grid
If you want to use this data in your Grid layer for instance to label it with the count per category, you can do the following:
Create an expression based label with this expression:
array_to_string(overlay_contains(layer:='count_layer',expression:='\n' || category || ' : ' || count ))
