Can I pull deeply-nested nodes from a Relay query result?

743 views Asked by At

My goal is to combine multiple nested sibling arrays of edges from the same Relay fragment into one render call.

Backend data is delivered via Relay containers (also using react-router-relay).

I have a component hierarchy that displays multiple Tags from several different Users. The top-level query looks something like this:

getUserGroup(id: 'abc') {
  users {
    edges { // array of arrays objects with nested arrays
      node {
        Tags {
          edges { // array of objects with target items
            node {
              tagName // target item
              id
            }
          }
        }
      }
    }
  }
}

Which results in something like this (in fully-flattened form):

  results = [['tagname1', 'tagname2'], [tagname3, tagname4]]

Currently I render a hierarchy with each node Type in its own Component, i.e., Tagmenu -> UserTagGroup -> TagItems (code is at the bottom of this post).

This groups all of the tags by User, eventually rendering a list like this:

User 1:
  - Tag 1
  - Tag 2
User 2:
  - Tag 3
  - Tag 4

What I'd like to achieve is a render where all Users' tags are mixed-in together at the second tier, i.e., a hierarchy like TagMenu -> TagItems, to render:

User Tags
  - Tag 1
  ...
  - Tag 4

The only way I can manage so far is to manually extract and combine all of arrays from the top-level Relay container results with something like this (pseudo code):

for each array in users.edges:
  for each array in node.Tags.edges:
    return node.tagName

This doesn't seem right for 2 reasons:

  1. It's a bit much to pack into a render() function,
  2. It's not clear if it's possible to protect against null refs with default props in Relay

... but it's obviously doable.

My question is: what's the Relay way to do this? Given how naturally the library leads to component composition, I can't imagine that pulling deeply nested results at a higher level and manually shuffling them is optimal. Here are my components:

// TagsMenu component, top level
class TagsMenu extends Component {
  render() {
    return (
      <div>
        {
          this.props.userGroup.users.edges.map(u => {
            return <UserTagGroup user={u.node} />
          })
        }
      </div>
    )
  }
}

fragment on UserGroup {
  users(first: 1000) {
    edges {
      node {
        ${UserTagGroup.getFragment('user')}
      }
    }
  }
}


// UserTagGroup, second component
class UserTagGroup extends Component {
  render() {
    return (
      <div>
        <h4>User:  {this.props.user.id}</h4>
        {
          this.props.user.Tags.edges.map(t => {
            return <TagMenuItem tag={t.node} />
          })
        }
      </div>
    )
  }
}
fragment on User {
  id
  listingTags(first: 1000) {
    edges {
      node {
        ${TagMenuItem.getFragment('tag')}
      }
    }
  }
}


// TagMenuItem, bottom level component. Renders 1 Tag.
class TagMenuItem extends Component {
  render() {
    return (
      <div>
        {this.props.tag.tagName}
      </div>
    )
  }
}
fragment on Tag {
  tagName
  id
}
1

There are 1 answers

0
yachaka On

What you've been describing - "manually extract and combine all of arrays from the top-level Relay container" - do seem like the way to go, though.

If the problem lies in having this function in the render() method, I suggest you to use a combination of state and the method componentWillReceiveProps(). The goal being to recompute the flattened list only when this.props.users has truly changed.

Something along theses line :

class MyRootContainer extends Component {

  constructor(props) {
    super(props);

    this.state = {
      flattenedList: this.computeFlattenedListFromProps(this.props),
    };
  }

  componentWillReceiveProps(props) {
    if (this.props.users !== props.users) {
      this.setState({
        flattenedList: this.computeFlattenedListFromProps(props),
      });
    }
  }

  computeFlattenedListFromProps(props) {
    // Compute and return flattened list
  }

  render() {
    ...
  }
}