react-admin-firebase icon indicating copy to clipboard operation
react-admin-firebase copied to clipboard

ReferenceField for nested collections?

Open hasbean opened this issue 5 years ago • 24 comments

First thanks a lot for this beautiful creation.

I have a Users collection and within it an Addresses collection like the image below

issuetest

I'm trying to create a ReferenceField where I can get the "name" field in that user's address and be able to click through it and see all the user's addresses.

I'm guessing what I'm asking is that am I able to use paths? such as "/users/1/addresses/3" and retrieve those fields?

hasbean avatar Jan 17 '19 11:01 hasbean

Hi @hasbean,

Apologies for the late reply, if I understand you correctly, you want to have nested entities. As far as I'm aware, this is not an easy feature to implement using this framework, as this Stack Overflow answer suggests [1].

However, this would be very useful and I'm currently looking into a solution for this, most likely implementing a new Field which reads a custom data-provider request type.

Cheers, Ben

[1] Add Nested resource using admin-on-rest

benwinding avatar Feb 10 '19 11:02 benwinding

After some more research, I've found that this will be very difficult to implement a general solution in which sub collections of a document could be used.

For the time being, This article shows some examples of how to organise your data in a flat style.

I'll update this issue, when I hear back from the react-admin team about how this could be implemented though.

benwinding avatar Feb 10 '19 23:02 benwinding

@benwinding Here is an example of what I used to get this working. React-admin already has a ReferenceField which I took as a template for creating this one. It does introduce the classnames and @material-ui/core/styles dependency.

import React, { useEffect, useState } from 'react'
import { LinearProgress, Link } from 'react-admin'
import classnames from 'classnames'
import { withStyles } from '@material-ui/core/styles';

const styles = theme => ({
  link: {
    color: theme.palette.primary.main,
  },
});

const stopPropagation = e => e.stopPropagation();

const FirebaseReferenceField = ({ reference, source, record = {}, classes = {}, children, className, ...rest }) => {
  const [referenceRecord, setReferenceRecord] = useState({})

  async function getReference() {
    const result = await record[source].get()
    setReferenceRecord({
      id: result.id,
      ...result.data()
    })
  }

  useEffect(() => {
    getReference()
  }, [])

  if (referenceRecord) {
    return (
      <Link
        to={ `/${ reference }/${ referenceRecord.id }` }
        className={className}
        onClick={stopPropagation}
      >
        { React.cloneElement(children, {
          className: classnames(
            children.props.className,
            classes.link // force color override for Typography components
          ),
          record: referenceRecord,
          resource: reference,
          ...rest
        }) }
      </Link>
    )
  }
  return <LinearProgress/>
}

const EnhancedFirebaseReferenceField = withStyles(styles)(FirebaseReferenceField);

EnhancedFirebaseReferenceField.defaultProps = {
  addLabel: true,
};

export default EnhancedFirebaseReferenceField;

You can then use it the same way as ReferenceField:

<FirebaseReferenceField label="App" source="app" reference="apps">
  <TextField source="name"/>
</FirebaseReferenceField>

I've not really looked into editing a reference yet, as my requirement was only showing them.

EDIT: removed a console.log in the code EDIT 2: This does require React Hooks (since version 16.8)

wezzle avatar Mar 06 '19 13:03 wezzle

Hi @wezzle,

Thanks for sharing! That's a great start, and I'd like to use that as a base for an upcoming release (if you don't mind). The problem is, react-admin can't handle sub-resources (references of references):

Won't work

/users/:id/payment/:id
resource=users
sub_resource=payment
id=id

But we might be able to hide that from the provider, by flattening out the resource path and not using slashes in the route, something like this.

Might work

/users%:id%payment/:id
resource=users%:id%payment
id=id

What do you think? not sure if this is possible (or makes sense to anyone)

Cheers, Ben

benwinding avatar Mar 07 '19 08:03 benwinding

Ah, sorry, appears I didn't read what the exact issue was. As I'm neither really a Firebase nor React-admin power user I've not used subcollections yet so I think I can't help you with that.

Feel free to use the code as a base.

wezzle avatar Mar 07 '19 08:03 wezzle

Hi @wezzle,

Thanks for sharing! That's a great start, and I'd like to use that as a base for an upcoming release (if you don't mind). The problem is, react-admin can't handle sub-resources (references of references):

Won't work

/users/:id/payment/:id
resource=users
sub_resource=payment
id=id

But we might be able to hide that from the provider, by flattening out the resource path and not using slashes in the route, something like this.

Might work

/users%:id%payment/:id
resource=users%:id%payment
id=id

What do you think? not sure if this is possible (or makes sense to anyone)

Cheers, Ben

Your approach makes sense. FWIW.. Loosing access to collections in Firestore is a big miss, defeating a major reason for using Firestore in the first place.

tfesslerCWC avatar May 15 '19 19:05 tfesslerCWC

Thanks @tfesslerCWC,

Loosing access to collections in Firestore is a big miss, defeating a major reason for using Firestore in the first place.

I agree, which is why I spent a lot of time developing a possible solution, but ran into a many problems integrating it into the react-admin framework. The main problem is that react-admin has no concept of what a nested resource is. Our current options are:

  • Wait for react-admin to implement the nested resources, but it's not likely (see this issue)
  • Fork react-admin to create a similar framework, which includes nested resources. This would diverge completely from react-admin with possibly zero compatibility.
  • Try hacking together another implementation.

Feel free to contribute any ideas or pull-requests you think would help accomplish this. Cheers, Ben

benwinding avatar May 16 '19 01:05 benwinding

@tfesslerCWC,

FYI, the recent update v0.6.1 (to this package) has the ability to change the root path of the Firestore Connection. This means you could change this to traverse Firestore and access any nested collection.

// Options are optional
const options = {
  // All collections and documents can be relative to the new root ref, rather than the firestore root
  rootRef: 'root-collection/document'
}

const dataProvider = FirebaseDataProvider(config, options);

However ReferenceFields of nested collections are still not possible across different levels of the database tree. Will leave this issue open for now, but I hope this helps a little! Cheers, Ben

benwinding avatar Jun 14 '19 06:06 benwinding

Is there a way to dynamically allow the user to change the root reference? I have a bunch of organizations and it would be nice to toggle between them from a dropdown and see their nested collections.

horsejockey avatar Nov 17 '19 18:11 horsejockey

@benwinding Since react-admin 3.0 and Collection Group queries (Docs) in Firestore are now released, there may be another way to handle nested collections.

What if, when declaring a Resource in <Admin />, a slash is added to the path and then there's a function that parses the path and uses that as the "root" for all queries to that resource?

<Resource name="programs/content" {...content} />

I imagine this could be a similar implementation to how you did the root collection document concept, just on a per Resource level.

Essentially what this would do is group all the subcollections into one collection and react-admin wouldn't know the difference. It would show up in the side bar just like a regular resource: image

But on the Firestore side the data would be stored as a subcollection.

At least in my case, in the admin view I actually do want to see all the content for all my the programs in one big grid list, but on the frontend (not using react-admin) I only want to see the content for a given program, which is why I want it as a subcollection and not a flat structure.

Does that make sense? What do you think?

davidstackio avatar Apr 25 '20 20:04 davidstackio

Hi @dhstack,

Thanks for bringing this up, I haven't heard of Collection Group queries, that's pretty cool, although It looks like there's a few gotcha's (requires indexes to be added, retrieves collections globally not just within a certain tree branch) It could be a cool feature to add to this library, it's just not clear how it will work as a CRUD resource.

  • Create - Which subcollection would the new document add to?
  • Read - Easiest can just do: db.collectionGroup('comments').get()
  • Update - Might be fairly easy if this is possible db.collectionGroup('comments').doc('ID').update(...)
  • Delete - Might be fairly easy if this is possible db.collectionGroup('comments').doc('ID').delete()

It would be good if we could somehow pass in a resource type flag to the resource, so the dataprovider can know what type to initialize ('COLLECTION', 'COLLECTION_GROUP' etc..) do you have any ideas about that?

Anyway, thanks for your help!

Cheers, Ben

benwinding avatar Apr 27 '20 01:04 benwinding

@benwinding I didn't even think about the other CRUD options besides read when I came up with this idea (oops)... soooo this may not work out at all. Especially since it seems that collection group queries are seem to be only designed for reading data.

In any case, determining the appropriate document to create/update/delete would be a challenge, as you've pointed out. Perhaps there's a way to determine the "root" document that a given subcollection document is part of? Maybe FieldPath.documentId() can be used for that.

If that can be used, I imagine the create/update/delete bits would look and act similar to the existing functions, but I'm sure there's some logic I'm missing here where the root document id is injected.

As for your question about a resource type, maybe when a specific identifier is added (e.g. a / before the name - so /comments), that would signal that a collection group query should be used instead of a regular one with a check for the special identifier when a resource is being accessed. Something like this is what I'm imagining for adding a resource to <Admin />:

<Resource name="/comments" {...} />

Regardless, it'd be awesome if we could use sub-collections somehow with react-admin-firebase since that's such a powerful feature of Firebase.

davidstackio avatar Apr 27 '20 02:04 davidstackio

@benwinding I didn't even think about the other CRUD options besides read when I came up with this idea (oops)... soooo this may not work out at all. Especially since it seems that collection group queries are seem to be only designed for reading data.

All good, it could be a good idea for a read-only view perhaps. We could get around it by using the entire path as the id field. Something like id=posts/213/comments/222. This would allow the dataprovider to know the full tree of which subcollection to send it to. However this could break other things I think.

Regardless, it'd be awesome if we could use sub-collections somehow with react-admin-firebase since that's such a powerful feature of Firebase.

I think we're getting closer! I'll think about it over the next few days. There's many things in the react-admin framework and redux in general that I've learnt since this issue was opened, hopefully we can get something working soon.

benwinding avatar Apr 27 '20 03:04 benwinding

@benwinding first thank you for your contribution to the community. This library is being very useful! This issue are solved or still can't manage nested collections?

liveki avatar Apr 30 '20 16:04 liveki

Hi @liveki,

This is an ongoing discussion, there's currently no good way to achieve nested resources as react-admin is built around single-level resources (see here for more). And there's no plans to develop nested resources into the system.

The creator of react-admin suggests to make the rest api handle nested resources, but in many cases this is not possible, unless react-admin resources can be created and removed dynamically? hmmm I'll check on that... Seems this is possible dynamically declared resources could be the solution!

Please contribute ideas

Feel free to contribute a proposed solution if you like, but read through this thread first to see if it's already been suggested.

Cheers, Ben

benwinding avatar Apr 30 '20 16:04 benwinding

Hey @benwinding,

One idea is to tackle a subproblem of this issue, which I believe is the most useful one: subcollections under users/{loggedInUser.uid}/subcollection. I opened an issue https://github.com/marmelab/react-admin/issues/5163 in react-admin, though I don't expect them to support it since it requires constraints on all forms of DataProvider (e.g. a REST API must adhere to the format users/{uid}/collection, which may be too restrictive). On the other hand, this restriction may be more accepted by Firebase users since, as others in this issue have pointed out, subcollections are powerful and are a major reason for using Firebase in the first place.

In essence, as the example in my react-admin issue gave, this approach allows users (perhaps from different organisations) to possess different data that only they have access to (i.e. multi-tenancy). On the technical side, you could have a new boolean field like multiTenancyMode in the options that's passed to FirebaseDataProvider. If it's set to true, all resources will be fetched from Firestore at users/{loggedInUser.uid}/{resourceName}

Not close at all to the ideal solution of supporting a general nested subcollection, but a way simpler problem with decent use, and one that can be solved independent of react-admin

seranotannason avatar Aug 17 '20 06:08 seranotannason

Hello @benwinding

I am currently encountering the same problem, do you mind looking at it and providing me with a feedback?

https://stackoverflow.com/questions/64195361/performing-a-crud-function-to-a-sub-collection-for-firestore

BasyiddW avatar Oct 05 '20 08:10 BasyiddW

Got anybody something working? Are <Resource/> within <Resource/> working?

Maybach91 avatar Mar 23 '21 08:03 Maybach91

@benwinding It looks like v4 of react-admin will support passing arbitrary metadata to the DataProvider, so there could be a solution there to allow passing along a sub-collection path, etc.

Specific issue: https://github.com/marmelab/react-admin/pull/7116

v4 Roadmap https://github.com/marmelab/react-admin/issues/5933

5hee75 avatar Mar 01 '22 06:03 5hee75

The react-admin-firebase README says:

Ability to manage sub collections through app configuration

That sounds like what the OP is looking for. Is there more information on how to do that?

wmadden avatar Jun 30 '22 09:06 wmadden

Hi @dhstack,

Thanks for bringing this up, I haven't heard of Collection Group queries, that's pretty cool, although It looks like there's a few gotcha's (requires indexes to be added, retrieves collections globally not just within a certain tree branch) It could be a cool feature to add to this library, it's just not clear how it will work as a CRUD resource.

* **Create** - Which subcollection would the new document add to?

* **Read** - Easiest can just do: `db.collectionGroup('comments').get()`

* **Update** - Might be fairly easy if this is possible `db.collectionGroup('comments').doc('ID').update(...)`

* **Delete** - Might be fairly easy if this is possible `db.collectionGroup('comments').doc('ID').delete()`

It would be good if we could somehow pass in a resource type flag to the resource, so the dataprovider can know what type to initialize ('COLLECTION', 'COLLECTION_GROUP' etc..) do you have any ideas about that?

Anyway, thanks for your help!

Cheers, Ben

@benwinding has there been any move towards adding subcollections to this project, I really this approach, please let me know if you would like some help with this.

pduggi-cf avatar Aug 01 '22 10:08 pduggi-cf

The react-admin-firebase README says:

Ability to manage sub collections through app configuration

That sounds like what the OP is looking for. Is there more information on how to do that?

Did you find a solution for that

myselfshravan avatar Aug 08 '22 22:08 myselfshravan

It's been a 3 years since this issue was created and I've been following it for the whole time. If my wish could come true this Christmas I'd wish to this issue would be closed and we have a solution to manage sub collections.

Merry Xmas everyone, best regards from Mongolia

abadrangui avatar Dec 22 '22 11:12 abadrangui

Hello everyone, I'm still a junior dev, sorry if I say something wrong...

but I went through the same problem, my solution was, use localStorage.setItem("subId", subId)

<Resource name={type/${localStorage.getItem("subId")}/clients} list={ListClients} />

edipo-moreira avatar Jan 01 '23 12:01 edipo-moreira