Im trying to export a named gql query to be used in another react component in order to call it to update the apollo cache. The code works if I use PRODUCTS_QUERY since I export const PRODUCTS_QUERY. However, I would prefer to use the named gql variable 'productsQuery' for better naming convention. However, when I use 'productsQuery' in place of PRODUCTS_QUERY, it will throw an error. Anyone has successfully used the named variable? Please advise. Thanks in advance
In Home.js
export const PRODUCTS_QUERY = gql`
{
products {
id
price
pictureUrl
name
}
}
`;
export default graphql(PRODUCTS_QUERY, { name: "productsQuery" })(Home);
In AddProduct.js
// import { PRODUCTS_QUERY } from "./Home";
import { productsQuery } from "./Home";
try {
let response = await this.props.mutate({
variables: {
name,
price,
picture
},
// - - - - - - - - THIS PART WORKS - - - - - - -
// update: (store, { data: { createProduct } }) => {
// const data = store.readQuery({ query: PRODUCTS_QUERY });
// data.products.push(createProduct);
// store.writeQuery({ query: PRODUCTS_QUERY, data });
// }
// - - - - - - - - THIS PART FAILS - - - - - - -
update: (store, { data: { createProduct } }) => {
const data = store.readQuery({ query: productsQuery });
data.products.push(createProduct);
store.writeQuery({ query: productsQuery, data });
}
});
console.log("RESPONSE", response);
} catch (err) {
console.log("ERROR", err);
}
this.props.navigation.navigate("Home");
};
Any way I can export in a manner which allows me to use named variable 'productsQuery' rather than 'PRODUCTS_QUERY' since purpose of naming is to make naming convention standardized. Thanks in advance
In your Home.js file you exported 2 things:
The query with the name PRODUCTS_QUERY
The graphql operation as the default
The way it is written now, you cannot import { productsQuery } since you didn't export anything with that exact name.
The name option you set in your graphql operation
({ name: "productsQuery" }) is not related to what you are describing. That option affects the prop name that the component will receive with the data from the query.
A few options to make it work:
Rename PRODUCTS_QUERY to productsQuery in Home.js and use the same import.
you can assign and export productsQuery instead of PRODUCTS_QUERY in your Home.js, like so:
export const productsQuery = PRODUCTS_QUERY;
If you don't want to change Home.js, you can change your import to something like:
import { PRODUCTS_QUERY: productsQuery } from "./Home";
There are probably more ways to do that.
You need to use as to rename a non-default export.
import { PRODUCTS_QUERY as productsQuery } from "./Home";
The syntax for renaming imports is a little different than renaming an argument in other js cases, this won't work:
// Incorrect
import { PRODUCTS_QUERY: productsQuery} from "./Home";
Related
(Edited)
Refetching with the generated query type seems to work different from the refetch function in Apollo Client useQuery. I don't understand how to phrase it - can anyone provide an example?
I'm realizing the problem is probably either my refetch is not properly phrased, or maybe the store is only hitting the cached query. I've been going over my code for days and I can't figure out what it could be. I've tried await blocks too.
The refetch worked with svelte-apollo, but i'm trying to eliminate that dependency. I've also tried Apollo Client's useQuery, but the whole point of graphql-codegen with typescript-svelte-apollo is to use the generated typescript wrapper for the query.
When I assign the generated query to a reactive constant in my Svelte front-end code,
$: observations = getObservations({ variables: { filter } });
the query does not refetch when i update the query variables, as I would expect.
This is how my svelte template is using the query. The filter object changes based on a form user input. I've tried this with an await block too.
<script lang="ts">
import { getObservations } from '$lib/generated';
$: observations = getObservations({ variables: { filter } });
function handleFilter(event) {
filter = event.detail;
}
</script>
{#if $observations.loading}
Loading...
{:else if $observations.error}
{$observations.error}
{:else if $observations.data}
{#each $observations.data['observations']['edges'] as edge}
<Item node={edge['node']} />
{/each}
{/if}
Since this plugin allows to use the query directly, without Apollo's useQuery, i'm not sure how to phrase a refetch.
If i do $observations.refetch(); inside handleFilter(e), i get an error
Property 'refetch' does not exist on type 'Readable<ApolloQueryResult<GetObservationsQuery> & { query: ObservableQuery<GetObservationsQuery, Exact<{ filter?: FilterObservationsInput; }>>; }>'.ts(2339)
There's nothing fancy in my config. Am I doing something wrong here?
schema: src/graphql/schema.graphql
documents:
- src/graphql/queries.graphql
- src/graphql/mutations.graphql
generates:
src/lib/generated.ts:
plugins:
- typescript
- typescript-operations
- graphql-codegen-svelte-apollo
config:
clientPath: src/lib/shared/client
# asyncQuery: true
scalars:
ISO8601Date: Date
ISO8601DateTime: Date
Here's the client:
export default new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
observations: relayStylePagination(),
},
},
},
})
});
The generated query:
export const getObservations = (
options: Omit<
WatchQueryOptions<GetObservationsQueryVariables>,
"query"
>
): Readable<
ApolloQueryResult<GetObservationsQuery> & {
query: ObservableQuery<
GetObservationsQuery,
GetObservationsQueryVariables
>;
}
> => {
const q = client.watchQuery({
query: GetObservationsDoc,
...options,
});
var result = readable<
ApolloQueryResult<GetObservationsQuery> & {
query: ObservableQuery<
GetObservationsQuery,
GetObservationsQueryVariables
>;
}
>(
{ data: {} as any, loading: true, error: undefined, networkStatus: 1, query: q },
(set) => {
q.subscribe((v: any) => {
set({ ...v, query: q });
});
}
);
return result;
}
Here's the query document that it's built from:
query getObservations($filter: FilterObservationsInput) {
observations(filter: $filter) {
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
id
createdAt
updatedAt
when
where
imgSrcThumb
imgSrcSm
imgSrcMed
thumbImage {
width
height
}
name {
formatName
author
}
user {
name
login
}
rssLog {
detail
}
}
}
}
}
I've followed Gatsby tutorial for Working With Images in Markdown Posts and Pages which is working well but what I want to achieve is to fetch image from a static location instead of using a relative path for the image.
Would like to reference image like this (in frontmatter)
featuredImage: img/IMG_20190621_112048_2.jpg
Where IMG_20190621_112048_2.jpg is in /src/data/img instead of same directory as markdown file under /src/posts
I've tried to setup gatsby-source-filesystem like this :
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/src/posts`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `data`,
path: `${__dirname}/src/data/`,
},
},
but graphQL query in post template fails :
export const query = graphql`
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
featuredImage {
childImageSharp {
fluid(maxWidth: 800) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
GraphQL Error Field "featuredImage" must not have a selection since
type "String" has no subfields.
Any idea how I could fetch image from a location distinct to the post markdown directory ?
Achieving this in Gatsby used to be pretty troublesome, but thanks to the new createSchemaCustomization Node API docs (since Gatsby 2.5) it's relatively easy.
Here's a demo where I replicate your repo structure: github
Here's where the relevant code lives: github
Here's the code to make it work:
// gatsby-node.js
const path = require('path')
exports.createSchemaCustomization = ({ actions }) => {
const { createFieldExtension, createTypes } = actions
createFieldExtension({
name: 'fileByDataPath',
extend: () => ({
resolve: function (src, args, context, info) {
const partialPath = src.featureImage
if (!partialPath) {
return null
}
const filePath = path.join(__dirname, 'src/data', partialPath)
const fileNode = context.nodeModel.runQuery({
firstOnly: true,
type: 'File',
query: {
filter: {
absolutePath: {
eq: filePath
}
}
}
})
if (!fileNode) {
return null
}
return fileNode
}
})
})
const typeDefs = `
type Frontmatter #infer {
featureImage: File #fileByDataPath
}
type MarkdownRemark implements Node #infer {
frontmatter: Frontmatter
}
`
createTypes(typeDefs)
}
How it works:
There are 2 parts to this:
Extend markdownRemark.frontmatter.featureImage so graphql resolves to a File node instead of a string via createTypes
Create a new field extension #fileByDataPath via createFieldExtension
createTypes
Right now Gatsby's inferring frontmatter.featureImage as a string. We'll ask Gatsby to read featureImage as a string instead, by modifying its parent type:
type Frontmatter {
featureImage: File
}
This is not enough however, we'll also need to pass this Frontmatter type to its parent as well:
type Frontmatter {
featureImage: File
}
type MarkdownRemark implements Node {
frontmatter: Frontmatter
}
We'll also add the #infer tag, which lets Gatsby know that it can infer other fields of these types, i.e frontmatter.title, markdownRemark.html, etc.
Then pass these custom type to createTypes:
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type Frontmatter #infer {
featureImage: File
}
type MarkdownRemark implements Node #infer {
frontmatter: Frontmatter
}
`
createTypes(typeDefs)
}
Now, we can fire up localhost:8000/___graphql and try to query the image
query Post {
markdownRemark {
frontmatter {
featureImage {
id
}
}
}
}
and we get...
Error: Cannot return null for non-nullable field File.id.
That is because while Gatsby now understands featureImage should be a File node, it has no idea where to get that file.
At this point, we can either use createResolvers to manually resolve the field to a File node, or createFileExtension to do the same thing. I choose createFileExtension because it allows more code reuse (you can extend any fields), while createResolvers, in this case, is more useful for a specific field. Seeing that all you want is to resolve a file from the src/data directory, I'll call this extension fieldByDataPath.
createFileExtension
Let's just look at the resolve attribute. It is a function that takes in the following:
source: The data of the parent field (in this case, frontmatter)
args: The arguments passed to featureImage in a query. We won't need this
context: contains nodeModel, which we'll use to get nodes from Gatsby node store
info: metadata about this field + the whole schema
We will find the original path (img/photo.jpg) from src.featureImage, then glue it to src/data to get a complete absolute path. Next, we query the nodeModel to find a File node with the matching absolute path. Since you have already pointed gatsby-source-filesystem to src/data, the image (photo.jpg) will be in Gatsby node store.
In case we can't find a path or a matching node, return null.
resolve: async function (src, args, context) {
// look up original string, i.e img/photo.jpg
const partialPath = src.featureImage
if (!partialPath) {
return null
}
// get the absolute path of the image file in the filesystem
const filePath = path.join(__dirname, 'src/data', partialPath)
// look for a node with matching path
const fileNode = await context.nodeModel.runQuery({
firstOnly: true,
type: 'File',
query: {
filter: {
absolutePath: {
eq: filePath
}
}
}
})
// no node? return
if (!fileNode) {
return null
}
// else return the node
return fileNode
}
We've done 99% of the work. The last thing to do is to move this to pass this resolve function to createFieldExtension; as well as add the new extension to createTypes
createFieldExtension({
name: 'fileByDataPath' // we'll use it in createTypes as `#fileByDataPath`
extend: () => ({
resolve, // the resolve function above
})
})
const typeDef = `
type Frontmatter #infer {
featureImage: File #fileByDataPath // <---
}
...
`
With that, you can now use relative path from src/data/ in frontmatter.
Extra
The way fileByDataPath implemented, it'll only work with fields named featureImage. That's not too useful, so we should modify it so that it'll work on any field that, say, whose name ended in _data; or at the very least accept a list of field names to work on.
Edit had a bit of time on my hand, so I wrote a plugin that does this & also wrote a blog on it.
Edit 2 Gatsby has since made runQuery asynchronous (Jul 2020), updated the answer to reflect this.
In addition to Derek Answer which allow assets of any type to be use anywhere (sound, video, gpx, ...), if looking for a solution only for images, one can use :
https://www.gatsbyjs.org/packages/gatsby-remark-relative-images/
The reason in your server schema you may have declared the featuredImage variable as string and in your client graphql query you are trying to call subobjects of the featuredImage variable and that subobjects is not existing.
You may have to check the graphql schema definition and align the query with the schema definition
you current schema might be like this
featuredImage: String
and you need to change it by declaring the proper types based on the requirements in the server side.
For more information about graphql types. please refer this url - https://graphql.org/learn/schema/#object-types-and-fields
Thanks
Rigin Oommen
I'm working with GraphQL and have a resolvers.js file that looks like this:
const User = require("../models/User");
const Post = require("../models/Post");
module.exports = {
Query: {
async users(){...},
async user(){...},
async posts(){...},
async post(){...},
},
User: {...},
Post: {...},
Mutation: {
createUser(){...},
login(){...},
createPost(){...},
},
}
But if I have more models, queries and mutations the file is gonna be very long. How can I split this into seperate files? One for user queries and mutations, one for posts and so. Or is that not possible? Maybe there's a way to combine this with the schema.js file? So that I can split the schema too and put schema/resolver from User into a file. I'm still a beginner in coding.
I found a way to do it very easy actually. In the schema.js I can use lodash merge to combine multiple resolver files and for the typedefs I just use an array. This way I can split everything into seperate files.
const { merge } = require("lodash");
module.exports = makeExecutableSchema({
typeDefs: [typeDefs, userTypeDefs],
resolvers: merge(resolvers, userResolvers)
});
Just in case somebody is looking for an answer in 2020,
I had the similar issue,
and I tried to adapt the method mentioned,
but found an easier way to solve the problem.
I used graphql-tools's mergeResolvers to solve the issue - https://www.graphql-tools.com/docs/merge-resolvers/
Example code would be like this
const { mergeResolvers } = require('#graphql-tools/merge');
const clientResolver = require('./clientResolver');
const productResolver = require('./productResolver');
const resolvers = [
clientResolver,
productResolver,
];
module.exports mergeResolvers(resolvers);
The lodash merge would not differentiate Query and Mutation,
thus throwing an error in my case.
Here is how I did it. Do note that this is in Typescript.
You would define your resolvers in separate files, such as this:
import { DateBidListResolvers } from "../../types/generated";
export const DateBidList: DateBidListResolvers.Type = {
...DateBidListResolvers.defaultResolvers,
list: (_, __) => { // This is an example resolver of Type DateBidList
throw new Error("Resolver not implemented");
}
};
Then you would aggregate them together in a single file like this:
import { Resolvers } from "../../types/generated";
import { Query } from "./Query";
import { User } from "./User";
import { DateBid } from "./DateBid";
import { DateItem } from "./DateItem";
import { Match } from "./Match";
import { Mutation } from "./Mutation";
import { Subscription } from "./Subscription";
import { DateBidList } from "./DateBidList";
import { DateList } from "./DateList";
import { Following } from "./Following";
import { MatchList } from "./MatchList";
import { Message } from "./Message";
import { MessageItem } from "./MessageItem";
import { Queue } from "./Queue";
export const resolvers: Resolvers = {
DateBid,
DateBidList,
DateItem,
DateList,
Following,
Match,
MatchList,
Message,
MessageItem,
Mutation,
Query,
Queue,
Subscription,
User
};
You could then import that resolvers export into your configuration setup:
import { resolvers } from './resolvers/index';
// ... other imports here
export const server = {
typeDefs,
resolvers,
playground,
context,
dataSources,
};
export default new ApolloServer(server);
I hope this helps!
Currently trying to switch from graphql-js to literal GraphQL types/schemas, I'd like to know if anyone has had any experience with this.
Let's take this really simple one :
const Person = new GraphQLObjectType({
name: 'Person',
fields: () => ({
name: {
type: GraphQLString,
description: 'Person name',
},
}),
});
I'd like to switch to the native GraphQL schema syntax i.e
type Person {
# Person name
name: String
}
However this would have to be incremental, and given the use of graphql-js, the best solution for now would be to parse GraphQL template literals to GraphQLObjectType (or any other type for that matter). Does anyone have experience doing this, I cannot seem to find any library for it unfortunately.
import { printType } from 'graphql';
printType(Person)
output:
type Person {
"""Person name"""
name: String
}
Here is the demo:
import { expect } from 'chai';
import { printType, printSchema, buildSchema, GraphQLSchema } from 'graphql';
import { logger } from '../util';
import { Person } from './';
describe('test suites', () => {
it('convert constructor types to string types', () => {
const stringTypeDefs = printType(Person).replace(/\s/g, '');
logger.info(printType(Person));
const expectValue = `
type Person {
"""Person name"""
name: String
}
`.replace(/\s/g, '');
expect(stringTypeDefs).to.be.equal(expectValue);
});
it('buildSchema', () => {
const stringTypeDefs = printType(Person);
const schema = buildSchema(stringTypeDefs);
expect(schema).to.be.an.instanceof(GraphQLSchema);
});
it('printSchema', () => {
const stringTypeDefs = printType(Person);
const schema = printSchema(buildSchema(stringTypeDefs));
logger.info(schema);
const expectValue = `
type Person {
"""Person name"""
name: String
}
`.replace(/\s/g, '');
expect(schema.replace(/\s/g, '')).to.be.eql(expectValue);
});
});
source code:
https://github.com/mrdulin/nodejs-graphql/blob/master/src/convert-constructor-types-to-string-types/index.spec.ts
You can use graphql-cli to extract a native graphql schema from a graphql server. All you need to do is..
Download the tool | npm i -g graphql-cli
Run graphql init in the directory of your project to
create .graphqlconfig file
Start your graphql server
Run graphql get-schema and this will generate a your schema in native graphql
SAMPLE .graphqlconfig
{
"projects": {
"my_sample_project": {
"schemaPath": "schema.graphql",
"extensions": {
"endpoints": {
"local": "http://localhost:8080/graphql"
}
}
}
}
}
We leverage the auto-generation of graphql schema/queries/mutations for our CI workflows.
I have a component that needs to query two entirely separate tables. What do the schema, query and resolver need to look like in this case? I've googled but haven't found examples yet. Thanks in advance for any info.
UPDATE:
On Slack I see there may be a way to use compose for this purpose, e.g.:
export default compose(
graphql(query1,
....),
graphql(query2,
....),
graphql(query3,
....),
withApollo
)(PrintListEditPage)
Is there a way to have multiple declarations like this:
const withMutations = graphql(updateName, {
props({ mutate }) {
return {
updatePrintListName({ printListId, name }) {
return mutate({
variables: { printListId, name },
});
},
};
},
});
...that come before the call to export default compose?
The graphql function takes an optional second argument that allows you to alias the passed down property name. If you have multiple mutations you can use the name property to rename mutate as needed:
import { graphql, compose } from 'react-apollo'
export default compose(
graphql(mutation1, { name: 'createSomething' }),
graphql(mutation2, { name: 'deleteSomething' }),
)(Component)
For more details see the complete API.