React-apollo: Conditional queries for recursive components

Created on 19 Mar 2017  路  8Comments  路  Source: apollographql/react-apollo

Introduction

I am trying to make graphql powered comment section where replies can be infinitely nested.

What I did

type Comment {
  id: String!
  content: String!
  hasReplies: Boolean!
  replies(orderBy: CommentOrderFn!): [Comment]
}

```graphql

import "graphql/fragments/CommentFields.gql"

query CommentsQuery($after: String, $count: Int!, $orderBy: CommentOrderFn!) {
comments(after: $after, count: $count, orderBy: $orderBy) {
edges {
node {
... CommentFields
... on Comment {
hasReplies
replies(orderBy: $orderBy) {
... CommentFields
... on Comment {
hasReplies
replies(orderBy: $orderBy) {
... CommentFields
... on Comment {
hasReplies
}
}
}
}
}
}
}
pageInfo {
hasNextPage
startCursor
endCursor
orderBy
}
}

Then I recursively render comments and end up with tree like structure

commentList
comment
comment
comment
comment
comment
comment
comment

### Problem
Problem I am really struggling with and haven't solved yet is how do I load replies on user input? Like you can see in query above I am loading comments only 3 levels deep. I want to load replies for comment by clicking on `expand replies` button or something like that.

commentList
comment
comment
comment
comment
comment (+ expand replies)
comment <--- newly loaded comment/reply in response to clicking expand replies link
comment
comment

How would you go about solving this problem? Is something like this even possible?

### Additional info
Just for the completion this is my schema
```graphql
interface IEntity {
  id: String
}
interface IEdge {
  node: IEntity
  cursor: String
}
interface IPageInfo {
  hasNextPage: Boolean
  startCursor: String
  endCursor: String
  orderBy: String
}
interface IPaginated {
  edges: [IEdge]
  pageInfo: IPageInfo
}

type Query {
  comment(id: String!): Comment
  comments(after: String, count: Int!, orderBy: CommentOrderFn): IPaginated
}

And I have two react components one is <CommentList /> other is <Comment />. CommentList have CommentsQuery on it and Comment is dumb component.

Most helpful comment

I solved it. All I had to do is move isExpanded state to HOC so it is passed to props and skip can correctly calculate its value.

All 8 comments

I think you have two choices: send a flattened list and build a tree on the client, or only fetch x levels per query.

I think the first option will be the easiest.

@kolpav I think @wmertens' suggestion of flattening the tree is easiest. This will also be more efficient to render on the client and store on the server btw. What I'd suggest is basically turning the tree into a list by doing a preorder traversal while noting the level on each comment. Then on the client you can simply render the list and do the nesting with CSS.

I'm going to close this since it's not technically a question about react-apollo, but feel free to continue the discussion.

@wmertens @helfer Thanks for answers
I don't like flattening approach it would need to build the tree every time new comment is added or deleted and it would make reasoning about everything much harder.
I already have working comments section with standard redux inspired by this this example each comment is connected so it is very performant and I can add reply without rebuilding the entire comment tree which can get large.

It feels like grapql/apollo is not helping at all in this case, quite the opposite 馃槃

I thought I was close to solving this by using skip.
Top component <CommentList /> will load all comment with depth 3 and then each <CommentWithReplies comment={comment} /> component will have query just for the replies and also isExpanded which will be controlled by clicking on expand replies the comment itself without props would be passed through props. With this information I would be able to calculate if I should skip loading replies or not. But unfortunately skip doesn't work in such way it does not react to props change. If there was some api through which I would be able to control if query should run or not it would solve all my problems.

@kolpav How are you storing said tree in the database currently? Are you just using one huge JSON blob?

If GraphQL isn't helping, you can always use a custom JSON scalar type and store/retrieve the whole comment section in one piece. You'll lose the benefits of strong typing, but on the other hand you're free to store and retrieve whatever shape / depth of data you like.

Btw, you can control skip with props changes. You could also use state in your component, based on which a sub-component (extra comments) is either rendered or not. It shouldn't be too hard.

You can also do queries manually if you use withApollo, then you can just do client.query or client.watchQuery in any component.

@helfer

How are you storing said tree in the database currently?

I am storing comments in relational db like (simplified)

content
parentId

Btw, you can control skip with props changes. You could also use state in your component, based on which a sub-component (extra comments) is either rendered or not. It shouldn't be too hard.

Are you absolutely sure you can control skip with props change? This would mean I have done something wrong and was actually very close to loading just the replies for specific comment because approach you are describing is exactly what I have been doing without success. I will try again.

You can also do queries manually if you use withApollo

I don't know how I missed that 馃槥 I have read usage section many times but connecting data section just very briefly for some reason.. anyway this looks very promising.

Now I don't know which approach should I choose 馃槃 I'll try both and see how that goes.

Thank you guys!

I solved it. All I had to do is move isExpanded state to HOC so it is passed to props and skip can correctly calculate its value.

Awesome! Glad it worked out.

Was this page helpful?
0 / 5 - 0 ratings