0

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
}
Community
  • 1
  • 1
Brandon
  • 7,736
  • 9
  • 47
  • 72

1 Answers1

0

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() {
    ...
  }
}
yachaka
  • 5,429
  • 1
  • 23
  • 35