Relay: Q: Polymorphic composition

Created on 14 Aug 2015  路  8Comments  路  Source: facebook/relay

I've got a hierarchy like:

<App>
  <Header/>
  < ... />
</App>

where < ... /> is filled in with a component by the router, depending on which route the user is visiting. Both <Header/> and < ... /> are Relay.Containers.

As far as I can tell, there is no way to make <App> a Relay.Container that includes fragments from <Header/> and < ... />, so I've resorted to sticking both into their own separate RootContainers, with their own routes.

Am I missing something? How does/would FB design this?

Thanks :smile:

question

Most helpful comment

@josephsavona unfortunately it doesn't help in following situations:

  1. Trully dynamic components when parent is not aware of all possible children (like Relay.RootContainer)
  2. Code splitting - when actual child component is loaded on demand and you don't have reference to it's container at static time

Would be great if Relay could provide some solution for these cases as well as they are fairly common.

All 8 comments

Great question. Relay has support for this, but it's an advanced feature and so we didn't initially document it. Note that the API is likely to change (but we'll mark it as deprecated before doing so). However, we'd appreciate your feedback on this API and any suggestions you may have.

Current API

Here's how you can construct a container whose child is dynamic based on the current route:

Relay.createContainer(App, {
  fragments: {
    foo: () => Relay.QL`
      fragment on Node {
        ${Header.getFragment('foo')}
        ${route => switch (route.name) {
          case 'RouteA': return ContainerA.getFragment('foo');
          case 'RouteB': return ContainerB.getFragment('foo');
          default: return null;
        }}
      }
    `,
  },
});

In addition to the standard forms of fragment composition documented here, you can also pass a value of the type

(route: {name: string} => ?Fragment;

Basically, a function that accepts an object with a name: string property, and returns an (optional) fragment.

(Proposed) Future API

The future API we've explored for this would use GraphQL _directives_:

...
foo: () => Relay.QL`
   ${Header.getFragment(...)},
   ${ContainerA.getFragment(...)} @relay(route: 'RouteA'),
   ${ContainerB.getFragment(...)} @relay(route: 'RouteB'),
`

This is great, thank you! I'll try it out and get back to you.

Wait... @josephsavona

foo: () => Relay.QL`
      fragment on Node {
        ${Header.getFragment('foo')}
        ${route => switch (route.name) {
          case 'RouteA': return ContainerA.getFragment('foo');
          case 'RouteB': return ContainerB.getFragment('foo');
          default: return null;
        }}
      }
    `,

Wouldn't this only let the composed fragments compose on Node? If ContainerB wanted a User instead, he wouldn't be able to fragment on that, right?

@devknoll With the Relay GraphQL spec most types will implement the Node interface, so this would be okay. In your example if RouteA matched, you'd end up with the following (valid) GraphQL:

fragment App on Node {
   ...Header
   ...ContainerB
}
fragment Header on ...
fragment ContainerA on User {...}

Yup! Just realized that. It looks like there may actually be a bug with graphql-ruby or at least my implementation. Thanks again, and sorry for bothering :smile:

For posterity, the actual query being generated actually looks more like this:

query Dashboard { 
  viewer {
    id,
    ...Header
  }
}

fragment ContainerA on Viewer {
  name,
  id
}

fragment Header on Node
{
  id,
  ...ContainerA
}

This looks like it should be syntactically the same as the above, but I think the first fragment on Node caused graphql-ruby to lose the Viewer type information, forcing it to fall back to a default-implemented resolve_type.

@josephsavona unfortunately it doesn't help in following situations:

  1. Trully dynamic components when parent is not aware of all possible children (like Relay.RootContainer)
  2. Code splitting - when actual child component is loaded on demand and you don't have reference to it's container at static time

Would be great if Relay could provide some solution for these cases as well as they are fairly common.

Was this page helpful?
0 / 5 - 0 ratings