Unable to create Relay Container. The value of fragment `kind` was expected to be a fragment, got `Fragment` instead - graphql

I'm trying to get a demo running using relay for the first time. I have my Relay compiler running ok and my relay graphql files generating, but when I run the application I get the following error:
Uncaught Invariant Violation: Could not create Relay Container for `EntityRow`. The value of fragment `kind` was expected to be a fragment, got `Fragment` instead.
The fragment in the 'EntityRow' is as follows:
export default createFragmentContainer(EntityRow, graphql`
fragment EntityRow_someEntity on SomeEntity {
id
title
description
}`);
This component renders an single entity. The next component up the tree then is the EntityList component, whose fragment is:
export default createFragmentContainer(EntityList, graphql`
fragment EntityList_viewer on SomeEntity {
...EntityRow_someEntity
}
`)
Finally, the root of the component tree then:
const AppRootQuery = graphql`
query RelayDemoQuery {
entity {
...EntityList_viewer
}
}`
return (
<QueryRenderer
environment={relayEnvironment}
query={AppRootQuery}
...... />
In my generated EntityRow_entity.graphql.js file the value of node.kind is 'Fragment', but Im assuming this is correct and the error is a bit of a red herring and something else is wrong?

Related

Gatsby: How to handle undefined fields in siteMetadata?

I want to build a site header based on having defined a logo field or not (if it is not defined it will be used the site title instead).
gatsby-config.js:
module.exports = {
siteMetadata: {
title: 'Hello Web',
logo: '/images/logo.png'
}
}
query:
const query = graphql`
{
site {
siteMetadata {
title
logo
}
}
}
The above works fine, but if I remove the logo field the build breaks.
I read the Schema Customization section on Gatsby documentation, but I didn't find anything that apply (am I missing something?).
My current solution is to set logo to some "dummy content". Although it works, it's clearly not an elegant solution and have some drawbacks as the project grows.
Screenshot:
Check out create types in this blog post. and fixing fields in the the docs.
createTypes can be used to define, fix, or extend a Gatsby GraphQL type that represents some of your app’s data. Think of it like an escape hatch to politely inform Gatsby of your data’s shape.
Gatsby infers the schema based on the data it has available at build time. If you try to query for a field that doesn't exist, you will get a build error.
With createTypes you can tell gatsby that the logo field is a nullable String on the SiteSiteMetadata type. Now if the field is not found in your data source, you will get a null value for it, but gatsby can successfully build.
// gatsby-node.js
exports.sourceNodes = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type SiteSiteMetadata implements Node {
logo: String
}
`
createTypes(typeDefs)
}
Note that the rest of the fields (title, description, author) don’t have to be provided, they will still be handled by Gatsby’s type inference.

Heroku Apollo Server throws "ServerParseError: Unexpected token < in JSON at position 0" only for some queries

I created a GraphQL wrapper for PokeAPI. My queries all work in development fine and most of them work in production. However, I have the following query that works in production for smaller start and end ranges, but throws "ServerParseError: Unexpected token < in JSON at position 0" when I try to query for all of the pokemon with a very large range. This error does not happen in development.
query {
allPokemon(start: 0, end: 964) {
id
name
}
}
My resolver in my GraphQL for allPokemon only hits one REST endpoint and comes back with an array of objects that have the following structure:
{
name: "charmander",
url: "https://pokeapi.co/api/v2/pokemon/4/"
}
My resolver maps over the resulting array to grab the name value and to parse the url value to grab the id number at the end of the string.
Not sure if this is relevant/necessary to include here, but I am using apollo-datasource-rest. I created a class component that extends RESTDataSource that has abstracted out my functions for my GraphQL resolvers. Then I simply call those methods inside of my resolvers. My allPokemon method inside this RESTDataSource component looks like this:
async getAllPokemon(start = 0, end = 964) {
const response = await this.get(`pokemon?offset=${start}&limit=${end}`);
const pokemonIds = response.results.map(pokemon =>
parseUrl(pokemon.url)
);
return pokemonIds;
}
parseUrl is a utils function I created that just takes a url and parses it to grab the number at the end of the url after the last /.
Then in my resolvers, I have the following:
const resolvers = {
Query: {
allPokemon: (parent, args, { dataSources }) => {
return dataSources.pokemonAPI.getAllPokemon(args.start, args.end);
}
}
}
I can't seem to figure out if this is an issue with Heroku or with Apollo Server. My guess was with Heroku since I have no problems in development getting the expected data for all of the queries. I thought perhaps Heroku must have some limitations as far as timing out or how how many iterations of the parsing function it can do, but have been unable to confirm this theory, let alone find a solution. Any help is appreciated!

Apollo GraphQL client query returns introspection result instead of data

I'm currently trying to get data from the Squidex API for a NextJS app, by using Apollo as GraphQL client.
On localhost, both in dev/production mode, everything works fine.
After deploying the app on Heroku, the same query returns the introspection schema as result, instead of the expected data. On a real example, by running a query like:
{
queryPageContents(search: "Home") {
...PagesFragmentsPageId
data {
...PagesFragmentsPage
...PagesFragmentsHome
}
}
}
${Pages.fragments.pageId}
${Pages.fragments.page}
${Pages.fragments.home}
}
Basically I'm asking various data about a webpage, using fragments and so on.
My expected data should be as follows - such as on localhost:
But I'm receiving this on Heroku instead of the above one:
Because of this, my app fails its rendering because my code looks for a JSON node called queryPageContents - as shown in the first screenshot - but It's currently receiving __schema as query result. So this causes a 500 error on front-end.
I googled around and I've found this graphql-disable-introspection that has to be installed on server-side. I don't know if it could solve this issue, but I'm not getting how this could happens.
Any advice about this?
Thanks everyone in advance.
As Sebastian Stehle from Squidex noticed, by deferring the page data initialization after a data loading check, the issue it beings solved. In a NextJS scenario like this:
[...]
const {loading, error, data} = useQuery(PAGE_QUERY.pages.home);
// Instead here, pages it's being moved after error/loading checking as follows
// Exception check
if (error) {
return <ErrorDb error={error} />
}
// DB fetching check
if (loading) {
return null;
}
// Data initialization here
const pages = data.queryPageContents;
return (
// Page
[...]
);
[...]

Apollo GraphQL - How do I use an RxJS Subject as a variable with Apollo Client?

My type-ahead search was working great with REST but I'm converting to GraphQL, which has its challenges.
As the user types a last name into a form field the suggested results display in a data table below. Each letter is handled by the RxJS Subject.
The var searchTerm$ is a type of RXJS observable called a Subject binds to the HTML. The following is called from the OnViewInit lifecycle hook in an Angular app. The search is by the database column last_name.
However, this results in a Bad Request 400 error as the view loads and search doesn't work. I thought maybe this calls for a subscription but everything I find on those is about using web sockets to connect to a remote URL and server. Where do I go from here?
I'm using the Angular Apollo client with Apollo Express but I would be happy with any JS solution and try to figure it out from there. The server side is Nestjs which just wraps Apollo Server.
const lastNameSearch = gql `
query ($input: String!) {
lastNameSearch(input: $input) {
first_name
last_name
user_name
pitch
main_skill_title
skills_comments
member_status
}
}`;
this.apollo
.watchQuery({
query: lastNameSearch,
variables: {
last_name: searchTerm$, // Trying to use the observable here.
},
})
.valueChanges
.subscribe(result => {
console.log('data in lastNameSearch: ', result);
}),
The schema on the server:
lastNameSearch(input: String!): [Member]
The resolver:
#Query()
async lastNameSearch(#Args('input') input: String) {
const response = await this.membersService.lastNameSearch(input);
return await response;
}
Edit:
The error from the Network panel in dev tools. Console message worthless.
{"errors":[{"message":"Variable \"$input\" of required type \"String!\" was not provided.","locations":[{"line":1,"column":8}],"extensions":{"code":"INTERNAL_SERVER_ERROR","exception":{"stacktrace":["GraphQLError: Variable \"$input\" of required type \"String!\" was not provided."," at getVariableValues
And this goes on showing properties and methods in the app for another 300 lines or so.
First, a big thank you to the amazing Daniel Rearden for his help on various questions as I and lots of others on SO learn GraphQL! He has patience!
As Daniel pointed out in comments I had a simple mistake. I'll point it out in the commented code below. However, the big issue was trying to use an observable, subject, or similar method as a variable. Even if the RxJS subject is emitting a string GraphQL will hate trying to use a large object as a var. So I had to use a little reactive programming to solve this.
Setup the observable:
public searchTerm$ = new Subject<string>(); // Binds to the html text box element.
Second, let's set this up in a lifecycle hook where we subscribe to the observable so it will emit letters one at a time as they are typed into an input box.
ngAfterViewInit() {
let nextLetter: string;
// -------- For Last Name Incremental Query --------- //
this.searchTerm$.subscribe(result => {
nextLetter = result; // Setup a normal variable.
this.queryLastName(nextLetter); // Call the GraphQL query below.
});
}
Last step we have the GraphQL query and consuming the returned data object. This works perfect to say type a 'p' into the form and get back from a db all the last names starting with 'p' or 'P'. Type 'r' and the results narrow to last names starting with 'pr', and so on.
private queryLastName(nextLetter) {
const lastNameSearch = gql`
query ($input: String!) {
lastNameSearch(input: $input) {
first_name
last_name
user_name
pitch
main_skill_title
skills_comments
member_status
}
}`;
this.apollo
.watchQuery({
query: lastNameSearch,
variables: {
input: nextLetter, // Notice I had used last_name here instead of input.
},
})
.valueChanges
.subscribe(result => {
// Put the data into some UI in your app, in this case
// an Angular Material data table.
// Notice how we get the data from the returning object.
// The avoids the dreaded "null" error when the shape of the
// returned data doesn't match the query. This put an array
// of objects into the UI.
this.dataSource.data = result.data['lastNameSearch'];
},
);
}

How to test GraphQL queries with fragments using jest

Problem: I would like to test a GraphQL query that lives in a .graphql file like this:
#import '../../fragments/Widget.graphql'
query WidgetFragment($id: ID) {
readWidgetFragment(id: $id) {
...Widget
}
}
To create a GraphQL schema with mocked resolvers and data, I use makeExecutableSchema and addMockFunctionsToSchema from graphql-tools.
To run the query from inside a jest test, my understanding is that I need to use the graphql() function from graphql-js.
This function needs the query as a string, so I tried two different ways, but neither of them worked:
Parse the .graphql file as a normal text file, giving me the raw string (using the jest-raw-loader in my jest config).
This gives me: Failed: Errors in query: Unknown fragment "Widget". when I run the query.
Parse the .graphql file into a query object using jest-transform-graphql. I believe this should be the right approach, because it should resolve any imported fragments properly. However, to execute the query, I need to pass query.loc.source.body to the graphql, which results in the same error message as option 1.
You can use this:
import { print } from 'graphql/language/printer'
import query from './query.gql'
...
print(query)
Use the initial approach with parsing it as a raw text, except:
use a recursive function with a path argument (assuming you could have nested fragments)
which uses regex to extract all imports beforehand to an array (maybe use a nicer pattern :) )
append the rest of the file to a string variable
then loop through imports, resolving the #imports and passing them to itself and appending the result to the string variable
Finally return the result to the main function where you pass it to the graphql()
Yes, this is quite a pickle. Even with imports correctly working (>= v2.1.0 for jest-transform-graphql, they get added to the query.definitions object, which is completely sidestepped when calling graphql with document.loc.source.body as query argument.
On the server end, graphql (function graphqlImpl) will reconstruct the document object using parse(source) - but it'll have zero knowledge of the imported fragment definitions...
As far as I can tell, the best bet is to stamp fragments to the query source before sending it to the server. You'll need to explicitly find all lines starting with #import and replace these with actual text content of the to-be-imported graphql file.
Below is the function that I use. (Not tested for recursive fragments)
// Async wrapper around dynamic `import` function
import { importQuery } from "./queries";
const importAndReplace = async (fileToImport, sourceDocument, line) => {
const doc = await importQuery(fileToImport);
const targetDocument = (await sourceDocument).replace(line, doc.loc.source.body);
return targetDocument;
};
// Inspired by `graphql-tag/loader`
// Uses promises because of async function `importQuery` used
export default async graphqlOperation => {
const { body } = graphqlOperation.loc.source;
const lines = body.split(/\r\n|\r|\n/);
const bodyWithInlineImports = await lines.reduce(
async (accumulator, line) => {
await accumulator;
const lineSplit = line.slice(1).split(" ");
return line[0] === "#" && lineSplit[0] === "import"
? importAndReplace(lineSplit[1].replace(/"/g, ""), accumulator, line)
: Promise.resolve(accumulator);
},
Promise.resolve(body)
);
return bodyWithInlineImports;
};

Resources