Tyler Sebastian
Tyler Sebastian

Reputation: 9448

Fragment cannot be spread here as objects of type "X" can never be of type "Y"

In this case, type "X" is Application and type "Y" is type "Node" - I can see why this is happening, but my understanding of Relay isn't enough to understand how to fix it. The query generated by Relay is

query {
    node(id: $some_id) {
        ...F0
    }
}

fragment F0 on Application {
    ...
}

I have a schema that looks like

query {
    application { 
        /* kind of a generic endpoint for fetching lists, etc */
        invites(token: $token) {
            name
        }
    }
    viewer { /* the current user */ }
}

I'm trying to fetch a specific invite from outside a session (viewer is null).

I've tried

const application = Relay.QL`query { application }`
...
<Route ... queries={{ application }}/>
...
Relay.createContainer(Component, {
    initialValues: { token: null },
    fragments: {
        application: () => {
            fragment on Application {
                invites(token: $token) {
                    ...
                }
            }
        }
    }
})

which gives me the error

fragment "F0" cannot be spread here as objects of type "Node" can never be of type "Application" - or something to that effect.

I'm a little confused, because if I were to write a raw query and run it through GraphQL directly

query {
    application {
        invites(token: "asdasdasd") {
            edges {
                node {
                    name
                }
            }
        }
    }
}

it gives me what I'm looking for...

In the backend, my graph is defined like

export const Application = new GraphQLObjectType({
  name: 'Application',
  fields: () => ({
    id: {
      type: GraphQLString,
      resolve: () => 'APPLICATION_ID'
    },
    invites: {
        type: InviteConnectionType,
        args: connectionArgs,
        resolve: (application, args) => {
            ...
        } 
    }
  })
})

export default new GraphQLSchema({
  query: new GraphQLObjectType({
  name: 'query',
  fields: {
    node: nodeField,
    application: {
      type: Application,
      resolve: (root, args, ctx) => {
        return Promise.resolve({})
      }
    }
  }
})

I've been looking at questions like this and some issues on the Relay github, but it's not clear to me how I should implement nodeInterface.

edit: the short-long of the current nodeInterface code is

export const {
  nodeInterface,
  nodeField
} = nodeDefinitions(
  (globalId) => {
    const { type, id } = fromGlobalId(globalId)
    return db[type].findById(id)
  },
  (obj) => {
    const name = obj.$modelOptions.name.singular
    return types[name]
  }
)

Application is not a db model, however, just a generic interface to fetch data through. I've tried checking to see if type === 'Application', and returning null (although I see why that doesn't work), returning Application (the GraphQLObject), but that doesn't work... not really sure where to go from there.

Upvotes: 1

Views: 7348

Answers (2)

Tyler Sebastian
Tyler Sebastian

Reputation: 9448

To give an update on this, I was on the right path.

The current nodeDefinitions I had just needed a little extra:

nodeDefinitions(
  (globalId) => {
    const { type, id } = fromGlobalId(globalId)

    if (type === 'Application') {
      return Promise.resolve(Application)
    }

    return db[type].findById(id)
  },
  (obj) => {
    if (obj.$modelOptions) {
      /* sequelize object */
      const name = obj.$modelOptions.name.singular
      return types[name]
    } else if (obj.name === 'Application') {
      return Application
    }

    return null
  }
)

I'm not sure if this is the best way to do it, but it seems to do the trick. The gist is that, if the type of node that I want to be returned is Application, I return the GraphQL object - { ... name: "Application" ... } we'll use the name field from this in the next step (the second callback in nodeDefinitions) to just re-return Application. I think you could return a "custom" object or something else - it doesn't matter so long as you return something unique that you can define a mapping from to a GraphQLObject type (required for the second callback).

Upvotes: 1

Zero
Zero

Reputation: 356

  • You need to automatically generate an unique global id for a GraphQL type that you want to refetch.
  • In nodeInterface you tell GraphQL how to map the id to the corresponding GraphQL object.
  • By the given server-side object nodeInterface identifies the GraphQL type.

Below is simplified example how it may look like with Application:

// nodeInterface.
var {nodeInterface, nodeField} = nodeDefinitions(
  (globalId) => {
    var {type, id} = fromGlobalId(globalId);

    // The mapping from globalId to actual object id and type.
    console.log('globalId:', id);
    console.log('type:', type);

    if (type === 'Application') {
      // getApplication is your db method to retrieve Application object.
      // With id you could also retrieve a specific db object.
      return getApplication();
    } else {
      return null;
    }
  },
  (obj) => {
    // Note that instanceof does an identity check on the prototype object, so it can be easily fooled.
    if (obj instanceof Application) {
      return ApplicationType;
    } else {
      return null;
    }
  },
);

// Application.
export const ApplicationType = new GraphQLObjectType({
  name: 'Application',
  fields: () => ({
    // Auto-generated, globally defined id.
    id: globalIdField('Application'),
    _id: {
      type: GraphQLString,
      resolve: () => 'APPLICATION_ID'
    },
    invites: {
        type: InviteConnectionType,
        args: connectionArgs,
        resolve: (application, args) => {
            ...
        } 
    }
  }),
  // Declaring nodeInterface.
  interfaces: [nodeInterface]
});

Note that during the initial fetch nodeInterface is not even executed, so if nodeInterface is returning nothing there won’t be errors at the initial fetch. If that doesn’t make sense or you’re still struggling you can post a link to the repo, I’ll look into it.

Upvotes: 2

Related Questions