Passing props back into props React / Meteor

I’ve been trying for the last couple of hours to make the following work to no avail.

In brief :

When the component loads I’m getting the value of a url parameter, using that to query a collection, set a value based on the returned document and then trying to pass that value into another prop which in turns makes a call to a different collection setting a second set of data to a separate prop.

I guess you could say I’m trying a very hacky way of faking a join with Mongo.

This is the export so you can see what I’m attempting but it’s not working, undefined always gets returned :

export default withTracker(props => {
  const profileHandle = Meteor.subscribe('usermeta');
  const profile = UserMeta.find({username: props.match.params.username}).fetch();
  const myFollowersHandle = Meteor.subscribe('followers');
  return {
    profileHandleReady: profileHandle.ready(),
    profile: UserMeta.find({username: props.match.params.username}).fetch(),
    myFollowersReady: myFollowersHandle.ready(),
    myFollowers: FollowersCollection.find({uid: props.profile.uid}).fetch()
  };
})(FollowersList);

The last call is what’s giving me problems :

myFollowers: FollowersCollection.find({uid: props.profile.uid}).fetch()

I’ve also tried it without using props, same result :

myFollowers: FollowersCollection.find({uid: profile.uid}).fetch()

I’ve come to the conclusion that it just isn’t supposed to work this way but everything else I’ve tried, setting state, running code in componentDidMount() and even trying conditional render statements all give the same result. Either props.profile.uid is undefined or an empty array.

If I console log it on componentDidUpdate() I can see the value after a couple of undefined ones first, but I just can’t get the data back before the final Collection call is made.

I thought .ready was supposed to address this but it doesn’t seem to be able to help me here.

Is there anyway of forcing the following to return completed before doing anything else?

  1. Subscribe to this collection -> Meteor.subscribe(‘usermeta’);
  2. Get the object -> const profile = UserMeta.findOne({username: props.match.params.username});
  3. Get the desired value from the object -> profile[0].uid or profile.uid (depending on how it comes back)

ThenI can finally pass the desired value into the second collection call inside withTracker -> myFollowers: FollowersCollection.find({uid: .profile[0].uid}).fetch()

Or is there a better way of achieving what I’m trying to do?

Do you really need reactivity for data from both collections?

If answer is no, then convert one or both into method calls.

If the answer is yes, then place the retrieval of Followers into a child component with a separate withTracker()

Hmmm interesting, no I don’t need reactivity for both, only the second call.

Hadn’t though about using a Method. Where would I put it though? Inside withTracker? In the constructor? Or in a lifecycle?

Separate your component into 2: (1) For fetching the profile; (2) For fetching the followers.

If you don’t need reactivity in fetching the profile, then fetch it from the constructor using a method call. From the method call, assign it into a state. Assign the state as props for the followers component wherein you can use reactivity

1 Like

Many thanks, will give that a whirl now.

Still getting undefined back.

This is what I’m trying, does it look right to you?

The method (inside Meteor.isServer) :

'get.profileId': function(v) {
      return UserMeta.find({username: v}, {uid:1, _id:0}).fetch();
}

This should find a document where username = v and only return the uid value.

And in the constructor I’m doing this :

constructor(props) {
    super(props);

    Meteor.subscribe('profileMeta');
    console.log(Meteor.call('get.profileId', props.match.params.username))
}

Also tried this (same result) :

console.log(Meteor.call('get.profileId', this.props.match.params.username))

The subscription must be inside withTracker() of a new child component. What is inside the constructor is the Method call of the parent component.

Okay so I have to use two components to achieve this then?

Is there better alternative? I’ve seen aggregate but it looks super complicated.

What is your definition of better?

I was trying to keep my list of components as slim as possible, but you were right. It’s working now I’ve split the components up.

If you just want to compute a join, then you probably want to do that on the server side. See this question and answer on SO: https://stackoverflow.com/questions/26398952/meteor-publish-publish-collection-which-depends-on-other-collection/26421379#26421379

You should be able to use the URL parameter as parameter to that new publication you create for the join.

Your withTracker function should look like this:

export default withTracker(props => {
  const returnObj = {
    profileHandleReady: false,
    profile: null,
    myFollowersReady: false,
    myFollowers: [],
  };
  const profileHandle = Meteor.subscribe('usermeta');
  if (profileHandle.ready()) {
    returnObj.profile = UserMeta.find({username: props.match.params.username}).fetch();
    returnObj.profileHandleReady = true;
  }
  
  const myFollowersHandle = Meteor.subscribe('followers');
  if (myFollowersHandle.ready()) {
    returnObj.myFollowersReady = true;
    if (returnObj.profile) {
      returnObj.myFollowers = FollowersCollection.find({uid: returnObj.profile.uid}).fetch();
    }
  }

  return returnObj;
})(FollowersList);