What you were expecting:
A link to a resource with filters in the query parameters should lead to a functional list view.
<Link to={{
pathname: `/foreignResource`,
search: `filter={"filterKey": "filterValue"}`,
}} onClick={onClick}>{total}</Link><br />
What happened instead:
The http request for the list has the following qs params and results in a 400 status
filter: {"filterKey":"filterValue"}
limit: NaN
offset: NaN
sort: [null,null]
Steps to reproduce:
Take that you have a user resource and a usergroup resource.
The user resource List has a filter on its userGroupId field via ReferenceInput
On the list of usergroups, you want to show the number of users in each group.
The usergroup list is like this :
// UserGroupList.js
import React from 'react';
import {
List
, Datagrid
, TextField
, ReferenceManyField
, Link
} from 'react-admin';
import CountReferenceManyField from './CountReferenceManyField';
export const UserGroupList = props => (
<List {...props}>
<Datagrid>
<TextField source="name" />
<CountReferenceManyField
label="Number of users"
source="id"
reference="user"
target="userGroupId">
</CountReferenceManyField>
</Datagrid>
</List>
);
// CountReferenceManyField.js
import React from 'react';
import {
ReferenceManyField
, Link
} from 'react-admin';
const CountField = ({ loadedOnce, total, basePath, referenceRecord }) => {
if( loadedOnce && referenceRecord && referenceRecord.id ) {
const filter = {
"userGroupId": referenceRecord.id
};
return (
<>
<Link
to={{
pathname: `${basePath}`,
search: `filter=${JSON.stringify(filter)}`,
}}
onClick={event=>event.stopPropagation()}
>
{total} (search)
</Link>
<br />
<Link
to={{
pathname: `${basePath}`,
search: `filter=${JSON.stringify(filter)}&sort=id&order=ASC&page=1&perPage=25`,
}}
onClick={event=>event.stopPropagation()}
>
{total} (search and params)
</Link>
<br />
<Link
to={{
pathname: `${basePath}?filter=${JSON.stringify(filter)}`,
}}
onClick={event=>event.stopPropagation()}
>
{total} (pathname)
</Link>
</>
)
}
return null;
}
CountField.propTypes = {
referenceRecord: PropTypes.object
}
export const CountReferenceManyField = props => {
const { record } = props;
return (
<ReferenceManyField {...props}>
<CountField referenceRecord={record} />
</ReferenceManyField>
)
}
Related code:
This will route you to /user?filter={"userGroupId":"1"} which is ok.
<Link
to={{
pathname: `${basePath}`,
search: `filter=${JSON.stringify(filter)}`,
}}
onClick={event=>event.stopPropagation()}
>
{total} (search)
</Link>
But the list will send a request with the right filter, but with NAN as limit and offset, and with sort:[null,null]
**NB : if you reload the page with that url, all parameters are fine, even if the list has alwaysOn filters and/or filterDefaultValues
This, below, will work perfect but you have to pass all the parameters by hand and this is a no-go.
<Link
to={{
pathname: `${basePath}`,
search: `filter=${JSON.stringify(filter)}&sort=id&order=ASC&page=1&perPage=25`,
}}
onClick={event=>event.stopPropagation()}
>
{total} (search and params)
</Link>
And finally, this works ok but IS not ok. It will change the pathname, embedding the querystring and resulting in a "Page missing" display for a second.
<Link
to={{
pathname: `${basePath}?filter=${JSON.stringify(filter)}`,
}}
onClick={event=>event.stopPropagation()}
>
{total} (pathname)
</Link>
Other information:
Environment
I鈥檒l remove the irrelevant code asap
Thanks for the detailed report. I'll investigate asap
A custom link to a list should include not only the filter parameter, but also the pagination fields. See https://github.com/marmelab/react-admin/blob/48145fafa4c7494c26ab61e1f61ae15045e97a5d/examples/demo/src/segments/LinkToRelatedCustomers.js for an example.
I thought we were merging params from different sources to avoid this though. See https://github.com/marmelab/react-admin/blob/master/packages/ra-core/src/controller/ListController.js#L172 and its comment
We're doing this using precedence rules on sources, not on individual params. If there is a query param, we use it instead of the default params. That explains why the query must include all necessary params.
Can we consider this an enhancement then ?
Well, yes, but it's one that I really don't want to dig into. Merging 4 data sources is already very complex, if we multiply these merge rules by the 3 params (sort, pagination, filter), the ListtController will become unmanageable.
@djhi Does the fact that a full refresh works perfectly help you on this ?
@fzaninotto A way to get the list's default params from the state might be enough...
I should mention that this is a problem because of alwaysOn filters and / or filterDefaultValues.
The pagination params don't matter as much.
@fzaninotto Isn't a merge enough for this? (https://lodash.com/docs/4.17.11#merge for example)
no, there are plenty of cases when a simple merge feels like a bug for the user. E.g default values can be seen as a "merge" of the user values on top of the default values. But if the user erased a default value, hen this empty value should be used instead of the default one - and a simple merge doesn't do that.
Ok, but what about applying merge on everything but filters if they are provided ?
I don't know. We must start from the different use cases, then define a strategy for missing the 4 inputs, and see if this strategy works for all cases.
It works on master o_O
No idea when this started to work.
We just need to update the documentation to say it's possible.