Search code examples
reactjsreact-admin

Datagrid for nested endpoints in react-admin


I am trying to understand what the right approach would be to address nested endpoints, let's assume I have a many to many books and authors relationship, and an API which exposes api/authors, api/books, and api/authors/{id}/books. This is a common design pattern.

The CRUD on api/authors works beautifully in react-admin. However, under the authors <Show> I want to show a <Datagrid> of all the books with pagination and sorting, which my api makes available under api/authors/{id}/books.

What is the right approach to make a datagrid of such a nested endpoint?

I've looked into the <ReferenceManyField> which works well in the one to many context but doesn't allow accessing nested endpoints, only filtering an endpoint.

Ideally I would want something that is along the lines of:

<Show {...props}>
    <TabbedShowLayout>
        <Tab label="Books">
            <NestedResourceField reference="books" nestedResource={`authors/${props.record.id}/books`} pagination={<Pagination/>} >
                <Datagrid>
                    <TextField source="name" />
                </Datagrid>
            </NestedResourceField>
        </Tab>
    </TabbedShowLayout>
</Show>

Note that <NestedResourceField> is a hypothetical component which would have a behavior very similar to <ReferenceManyField> but would accept a nested endpoint under nestedResource instead of target.

I am struggling to understand what the design strategy should be for the hypothetical <NestedResourceField> in order to re-use as much of the react-admin framework as possible.

It would be straightforward to "manually" do the fetch myself and list the content but then I would lose all the pagination, filtering, sorting, etc... that comes with react-admin and the fact that books is an already defined resource.

My question is similar to these unanswered questions:

custom routes in react-admin

custom path for resource route in react-admin

Edit

Turns out an almost identical question which I had not found previously was posted here: Support for resource nesting


Solution

  • So I decided to solve this with a hack in the dataProvider.

    Keeping with the above example and using the stock ReferenceManyField:

    <Show {...props}>
        <TabbedShowLayout>
            <Tab label="Books">
                <ReferenceManyField reference="books" target="_nested_authors_id" pagination={<Pagination/>} >
                    <Datagrid>
                        <TextField source="name" />
                    </Datagrid>
                </ReferenceManyField>
            </Tab>
        </TabbedShowLayout>
    </Show>
    

    I then modified my dataProvider, which is a fork of ra-jsonapi-client. I changed index.js under the case GET_MANY_REFERENCE from this:

          // Add the reference id to the filter params.
          query[`filter[${params.target}]`] = params.id;
    
          url = `${apiUrl}/${resource}?${stringify(query)}`;
    

    to this:

          // Add the reference id to the filter params.
          let refResource;
          const match = /_nested_(.*)_id/g.exec(params.target);
          if (match != null) {
            refResource = `${match[1]}/${params.id}/${resource}`;
          } else {
            query[`filter[${params.target}]`] = params.id;
            refResource = resource;
          }
    
          url = `${apiUrl}/${refResource}?${stringify(query)}`;
    

    So basically I just remap the parameters to the url for the special case where the target matches a hard coded regex.

    ReferenceManyField would normally have caused the dataProvider to call api/books?filter[_nested_authors_id]=1 and this modification makes the dataProvider call api/authors/1/books instead. It is transparent to react-admin.

    Not elegant but it works and doesn't seem to break anything on the front end.