Filtering a list of names with ReactJS using data from RelayJS without calling GrpahQL

77 views Asked by At

I'm stuck in wondering if I can filter a list of names which I receive from Relay and graphql-java server without the need of making calls, without making any changes in my GrpahQL schema and only using ReactJS for this purpose.
---MobX as a state management library can be a decision but I should first store all the Relay result.

1

There are 1 answers

0
Brandon On BEST ANSWER

Caveat emptor: I'm newish at Relay as well & struggling with these same concepts. But, given the relative dearth of accessible information on Relay, I thought it'd be helpful to try and layout the key concepts here. My understanding could be wrong, so I'd love it if anyone who found a mistake in my code/reasoning would comment/edit.

Filtering took awhile for me to 'click' as well. It depends on where you keep the data you'll use to filter, but let's assume the name field lives on your Users Type, and the query is something like this:

viewer {
  allUsers {
    edges {
      node {
        name
      }
    }
  }
}

And let's say your top-level NameList component looked like this:

class NameList extends Component {
  render() {
    return (
      <div>
        {this.props.users.edges
          .map(u => {
            <NameItem name={u.node} />
           })
         }
      </div>
    )
  }
}

Relay.createContainer(NameList, {
  initialVariables: { first: 10 },
  fragments: {
    users: () => Relay.QL`
    fragment on Viewer {
      allUsers(first: $first) {
         edges {
           node {
             ${NameItem.getFragment('user')}
           }
          }
        }
      }
    ` 
  }
})

And your NameItem setup was simply:

class NameItem extends Component {
      render() {
        return (
          <div>
            Name: {this.props.user.name}
          </div>
        )
      }
    }

Relay.createContainer(NameItem, {
  initialVariables: {},
  fragments: {
    user: () => Relay.QL`
    fragment on User {
      name
      }
    ` 
  }
})

Consider the generalizable pattern here:

The List Component

A List component takes a fragment on the top-level Type in the query--in this case, Viewer, from a Relay container.

List also inserts a fragment on behalf of its Item child at the level of the User Type.

In other words, it captures an array of User objects it's supposed to pass down to the Item component.

If this wasn't Relay, and instead was, say, Redux, this component might simply pass state.users to the Item component. You can do that because, at some point, you've manually extracted all your Users from your own back-end and loaded them into Redux. But since Relay does the hard thinking for you, it needs a teensy bit more information than Redux.

The Item Component

This is even more simple. It expects an entity of type User and renders the name. Besides syntax, the functionality here isn't much different from a similar component in a Redux setup.

So really, without the complexity of Relay on top, all you have is an array of items that you're rendering. In vanilla React, you'd simply filter the array prior to (or during) your call to .map() in render().

However, with Relay, the fragment handed to the child is opaque to the parent--i.e., the List is handing a blind package to the Item, so it can't make a decision on whether or not to pass it down based on the fragment's content.

The solution in this contrived example is pretty simple: just peel-off the name field at the parent and child level. Remember: Relay is about components telling GraphQL what data they need. Your List component needs whatever fields it intends on filtering on--no more, no less.

If we modify the above List container:

...
users: () => Relay.QL`
    fragment on Viewer {
      allUsers(first: $first) {
         edges {
           node {
             name
             ${NameItem.getFragment('user')}
           }
          }
        }
      }
    ` 

And then we update our render function:

 <div>
        {this.props.users.edges
          .map(u => {
             if (u.node.name == "Harvey") {
               <NameItem name={u.node} />
             }
           })
         }
 </div>

Then we've achieved basic filtering without needing mobx, more server trips, etc.