React-admin: Support Link to List with only filter query parameter

Created on 1 Feb 2019  路  14Comments  路  Source: marmelab/react-admin

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

  • React-admin version: 2.6.2
  • Last version that did not exhibit the issue (if applicable): -
  • React version: 16.7.0
  • Browser: Latest Chrome
  • Stack trace (in case of a JS error):
documentation

All 14 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kdabir picture kdabir  路  3Comments

fzaninotto picture fzaninotto  路  3Comments

Dragomir-Ivanov picture Dragomir-Ivanov  路  3Comments

Kmaschta picture Kmaschta  路  3Comments

kikill95 picture kikill95  路  3Comments