react-admin-firebase
react-admin-firebase copied to clipboard
ReferenceField for nested collections?
First thanks a lot for this beautiful creation.
I have a Users collection and within it an Addresses collection like the image below
![issuetest](https://user-images.githubusercontent.com/227214/51316216-033f2600-1a65-11e9-8fc4-f4df7458c5b0.png)
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?
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
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 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)
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
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.
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.
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 fromreact-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
@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
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.
@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:
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?
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 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.
@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 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?
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
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
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
Got anybody something working? Are <Resource/>
within <Resource/>
working?
@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
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?
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.
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
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
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}
/>