redux-firestore icon indicating copy to clipboard operation
redux-firestore copied to clipboard

feat(reducers): support reference data type

Open jeongsd opened this issue 8 years ago • 9 comments

hello

Do you want to request a feature or report a bug?
feature

What is the current behavior? data type

db.collection("posts").doc("QyJJKtQhNA2QeHFVqfCR") = {
  content: String,
  author: Reference // 'users/jRjjlMyTBENjvln3pQpGwKgdvAa2'
}

query

{ collection: 'posts', doc: postId }

result

{ content: '', author: DocumentReference }

A DocumentReference refers to a document location in a Firestore database and can be used to write, read, or listen to the location. The document at the referenced location may or may not exist. A DocumentReference can also be used to create a CollectionReference to a subcollection.

image

What is the expected behavior?

{ content: '', author: { displayName: '', avatarUrl: '' } }

jeongsd avatar Feb 13 '18 12:02 jeongsd

I am aware that it's not a good idea, but I'm using it with react-redux-firebase as shown below, as a temporary solution.

import { compose, withProps, renderComponent } from "recompose";
import { connect } from "react-redux";
import { firestoreConnect, getVal, isLoaded } from "react-redux-firebase";

const spinnerWhileLoading = isLoading =>
  branch(
    isLoading,
    renderComponent(() => <div>spinner</div>)
  )

export default compose(
  firestoreConnect(({ postId }) => [{ collection: "posts", doc: postId }]),
  connect((state, { postId }) => ({
    post: getVal(state.firestore, `data/posts/${postId}`)
  })),
  spinnerWhileLoading(props => !isLoaded(props.post)),
  firestoreConnect(({ post }) => [
    { collection: "users", doc: post.author.id }
  ]),
  connect((state, { post }) => ({
    author: getVal(state.firestore, `data/users/${post.author.id}`)
  })),
  spinnerWhileLoading(props => !isLoaded(props.author)),
  withProps(({ post, author, ...props }) => ({
    post: { ...post, id: props.post, author }
  }))
)(PostComponent);

jeongsd avatar Feb 13 '18 14:02 jeongsd

@jeongsd The solution you provided is actually a great way to do it and is currently what I have done too.

redux-firestore hasn't handled reference objects for a few reasons:

  • Everything stored in redux state is currently only plain JS objects/arrays (i.e. not Classes or Objects with extra methods)
  • Storing strings with keys will work with populate from react-redux-firebase - though with a change like this might mean one could leave off the root parameter when calling populate on an object with document references (which sounds cool)

I am open to ideas for how things should change though

prescottprue avatar Feb 18 '18 09:02 prescottprue

I think the expected behavior from jeongsd would be great.

Just trying to implement the case that I have subcollection with references to another collection. Is there currently some easy method to call/populate that?

jollyBaker avatar Jun 03 '18 17:06 jollyBaker

@jollyBaker Not yet, but definitely high on the priority list.

prescottprue avatar Jun 04 '18 18:06 prescottprue

Hello!

Is there a workaround that would allow using populates with DocumentReference instead of just plain-string ids?

I would suggest allowing user to pass a function that would extract the document id from firestore attribute in case that it is not a plain-string but some complex structure. This would also cover the case of DocumentReference.

sergeytsivin avatar Mar 09 '19 07:03 sergeytsivin

Documentation for Populate has an example data which is using plain string IDs.

However, it would be helpful to consider the case when Firestore's built-in Reference data type is used for "linking", for example:

"todos": {
  "ASDF123": {
    "text": "Some Todo Item",
    "owner": DocumentReference("users/Iq5b0qK2NtgggT6U3bU6iZRGyma2")
   }
 },
 "users": {
   "Iq5b0qK2NtgggT6U3bU6iZRGyma2": {
     "displayName": "Morty Smith",
     "email": "[email protected]"
   }
}

In this case "owner" attribute always refers to the documents in "users" collection, so one could use plain string ids instead of references. However, as I am not allowed to change the data schema, I have to seek for a work around.

sergeytsivin avatar Mar 11 '19 18:03 sergeytsivin

Once I have realized that populates parameter could be a function I have created a workaround:


const collection = 'todos';
const populates = (dataKey, originalData) => {
    if (originalData && originalData['owner'] && !originalData['ownerData']) {
        originalData['ownerData'] = originalData.owner.path.split('/')[1];
    }
    return [{child: 'ownerData', root: 'users'}]
};

const mapStateToProps = state => {
    return {
        uid: state.firebase.auth.uid,
        todos: populate(state.firestore, collection, populates)
    }
};
const enhancer = compose(
    firestoreConnect([{collection, populates}]),
    connect(mapStateToProps),
);

This code first extracts the user document ID from the owner attribute and then uses this ID to initialize a new ownerData attribute. Finally ownerData attribute is populated as usual.

sergeytsivin avatar Mar 11 '19 18:03 sergeytsivin

Once I have realized that populates parameter could be a function I have created a workaround

Oh my, this helped so much! This isn't documented anywhere? I have some collection with documents whose IDs repeat userIDs in user collection and I wanted to populate these docs with user profiles by these IDs. This is the only way I found, am I right?

nt4f04uNd avatar Apr 08 '19 21:04 nt4f04uNd

@nt4f04uNd Not sure it is documented, but if it is, not clearly!

prescottprue avatar Apr 08 '19 21:04 prescottprue