Updating relay store for queries with multiple definitions - reactjs

Normally creating Relay query object for queries with single definition can be done using:
const relayQuery = Relay.QL `
query UserRoute($id_0: ID!) {
user(id:$id_0) {
id,
name,
email
}
}
;
I have query string intercepted from the one that are sent over network. They normally have multiple definitions (eg. query, fragment, mutation, subscription). I want to create a Relay query object for that type of query. The following code throws error:
Relay transform error "You supplied a GraphQL document named 'network' with 2 definitions, but it must have exactly one definition." in file '/Users/ankitshah/lyearnapp/src/network.js'. Try updating your GraphQL schema if an argument/field/type was recently added.
for this code:
const relayQuery = Relay.QL `
query UserRoute($id_0: ID!) {
user(id:$id_0) {
id,
...F0
}
}
fragment F0 on User {
id,
name,
email
}
;
I want this because I want to update Relay Store using Relay.Store.getStoreData().updateQueryPayload(queryObject, payload) function. Any help is appreciated.

I hit the same error. You will need to compact your query to be without fragments like so:
const relayQuery = Relay.QL `
query UserRoute($id_0: ID!) {
user(id:$id_0) {
id,
name,
email
}
}
;
Then it worked.

Try Using namedspaces . Eg
user1 :user(id:$id_0) {
id,
name,
email
}
user2 :user(id:$id_1) {
id,
name,
email
}

Related

Using variables in typeScript graphQL code generator

I followed this tutorial to auto generate the schema of my graphql endpoint in my front application.
However now I need to use variables, so I did something like this:
export const GET_TODO = gql`
query GetTodo($id: Int!) {
todos(id: $id) {
id
text
completed
}
}
}
I want to know if there is a way I can get the variable type "automatically" without having to redefine it in the front.
E.g.
...
query GetTodo($id: AutoGeneratedTodoIdType) {
...
PS: I am using react, in case there are framework/library specific solutions.
Yes you can, you will usually have exported types in the generated file such as GetTodoVariables

String interpolation in gql tag

I've already posted this as an issue in the graphql-tag repositoy but I'll post it here as I hope somebody on here has a workable solution.
What am I trying to do?
I'd like to dynamically define typedefs of local types based on props passed to my react component.
Why am I doing this?
I realise this is not the intended way of defining gql, however, I'm in the process of creating a React wrapper component around the Apollo Provider. The purpose of it is to make the process of mocking out data locally (as described here https://www.apollographql.com/docs/react/development-testing/client-schema-mocking/) more seamless with less boilerplate.
I'm going for a declarative approach where the user can simply define an array of fields (name, graphQL type and optional implementation that will default to a sensible faker implementation for the graphQL type) that will make local fields available directly on the Query type as well as an array of types (name, fields and an optional nested type) which should make it possible to define arbitrary local schemas declaratively with no boilerplate.
What's the outcome at the moment?
Just to establish a baseline, the following is working just fine and allows me to run codegeneration from Apollo CLI and query the new local test field (with a #client directive) in my application
const localTypeDefs = gql`
extend type Query {
test: String
}
`;
const client = new ApolloClient({
cache: new InMemoryCache(),
uri: "https://localhost:4000/graphql",
typeDefs: localTypeDefs,
...apolloClientOptions,
});
If I change the qgl definition to look like this instead
const name = "name";
const graphQLType = "String";
const localTypeDefs = gql`
extend type Query {
${name}: ${graphQLType}
}
`;
I get the following error when running codegen Syntax Error: Expected Name, found ":". If I change the gql definition to this
const testString = "test: String";
const localTypeDefs = gql`
extend type Query {
${testString}
}
`;
I get this error Syntax Error: Expected Name, found "}". on codegen. In general, it seems like everything after the interpolated string causes the compiler to emit this error on the next character. If I include the } in the testString, I get the error Syntax Error: Expected Name, found <EOF>..
Now, If I try to change to the function syntax instead so the localTypeDefs definition looks as follows:
const testString = "test: String";
const localTypeDefs = gql(`extend type Query { ${testString} } `);
The typedef is actually generated without any error, however, the codegeneration still fails with the error GraphQLError: Cannot query field "test" on type "Query" when I query for this field. The query hasn't change at all from the working baseline I posted at the top and if I change back to that without touching anything else, the codegen no longer complains about the query.
Curiously, if I change the above back to the baseline implementation but keep the explicit function call instead of the implicit `` string as the following:
const localTypeDefs = gql(`
extend type Query {
test: String
}
`);
I still get the error GraphQLError: Cannot query field "test" on type "Query" but as soon as I change back to the base case, everything is working just fine.
I was was able to get something similar to this working for a query by wrapping an input with double quotes:
const query = gql`
mutation RegenerateAgreement {
agreementsRegenerate(input: { userId: "${userId}" }) {
agreements {
body
prompt
}
}
}
`;

Send 2 identical Graphql queries in one call but with different variables

I have a search query which takes an variable and searches based on that.
On my home page I'd like to send this query 3 times with 3 different variables.
But if I do as above I can't get the results.
Here is the query:
const TOP_TEACHER_QUERY= gql`
query topTeachers {
searchBasicTeachersByRating(rating: 3) {
id
name
title
}
searchBasicTeachersByRating(rating: 4) {
id
name
title
isNew
}
}
}
and this is the function
allQueries() {
return this.apollo
.watchQuery<any>({
query: TOP_TEACHER_QUERY,
})
.valueChanges;
}
NOTE :
I have tried adding an interface and define the desired response data, but it has no effect
interface Response {
searchBasicTeachersByRatingMedium: Student[];
searchBasicTeachersByRatingHigh: Student[];
}
allQueries() {
return this.apollo
.watchQuery<any>({
query: TOP_TEACHER_QUERY,
})
.valueChanges;
}
THE DATA IS ONLY CONTAINING A LIST NAMED AFTER THE QUERY (searchBasicTeachersByRating)
I have tried the following query in graphql playground and it returns 2 arrays
but in Angular I can only get one
As a work around I created new queries at back-end, or sent 2 different queries.
But I want a solution for this approach.
Thanks in advance
When a selection set includes the same field multiple times, you need to utilize aliases for the duplicate fields:
searchBasicTeachersByRatingMedium: searchBasicTeachersByRating(rating: 3) {
id
name
title
}
searchBasicTeachersByRatingHigh: searchBasicTeachersByRating(rating: 4) {
id
name
title
isNew
}
After the request completes, each field will be available as a property on data under the provided alias. In the example above, we aliased both fields, but you could omit the alias from one of them.

Returning data from apollo-server based on a filter for a nested field

Is it possible to return data for a query in apollo that is based on a filter for a nested field? For example:
Query:
Users($filter: String!) {
user(filter: $filter) {
id,
name,
address(filter: $filter) {
street,
city,
country
}
}
}
TypeDefs:
Query: {
users(filter: String): [User]!
}
User: {
id: ID!,
name: String!,
address: Address
}
Address: {
street: String!,
city: String!,
country: String!
}
Apollo resolvers:
const resolverMap = {
Query: {
User(obj, args, context, info) {
// query api for User info (not including address)
// the final results of the query should only return users with the
// specific address (for example if the filter is country: England, only
// return users that live in England.
},
},
Address: {
address: (obj, args, context, info) {
// query another api for address based on parent (User) Id
},
},
}
Using that query, I only want results returned for users that reside in a specific country, say England. Since this filter is for a nested type [address], would it be possible using this query and not having to query address's first?
The short answer is no.
Filtering users is the user query's responsibility, not the children's. You are trying to split a query that's actually a single query (filter users by address).
If you want to modify the result of the user query itself, you should add an address parameter to the user query itself so it can filter the users by address.
Something like this:
Users($filter: String!) {
user(filter: $filter, addressFilter: $addressFilter) {
id,
name,
address {
street,
city,
country
}
}
}
Then the resolver look like this:
const resolverMap = {
Query: {
User(obj, args, context, info) {
// query both API for User info (not including address)
// and API for user addresses using $filter and $addressFilter
// Then match results from both API and discard users with invalid
// addresses
// For example if the filter is country: England, only
// return users that live in England with their address
},
},
Address: {
address: (obj, args, context, info) {
// you don't need this resolver unless you want to somehow
// change or format the addresses previously returned
},
},
}
If you still want to make it work the way you initially intended, you may find a way to hack it and make it work. You may be able to access the entire query object from the user resolver parameters, maybe inject stuff in the context to make it available in the resolvers, but even if you manage to do it you are kind of breaking the GraphQL semantics.
The user query is the one responsible for returning the list of users. After it returns the children resolvers can't change the user list itself again, only the children objects.
The address resolver will be called once for each user after the user query has already completed but it has nothing to do with modifying the result of the user query. The address resolver should only change address itself, but not the parent. address is a not a list, so it doesn't make sense to use it for filtering it, but you could use the address resolver to format each address in a particular way.

Automatic cache updates do not work in react apollo

The document says the cache can be automatically updated in the following example:
{
post(id: '5') {
id
score
}
}
mutation {
upvotePost(id: '5') {
id
score
}
}
Would the automatic update work in the following case when the cached object is in a list ? Like this:
This is a query that fetches a list of reviews:
{
reviewList(model:12){
list{
reviewId
text
}
cursor
}
}
When I update one of the reviews from the list to the server, react apollo does not automatically update the cached review:
mutation{
updateReview(reviewId:'1',text:'new text'){
reviewId
text
}
}
Is it a must to use update properties of the mutation component to update the cache?
Assuming you're using apollo-cache-inmemory, unless you are adding an item to or removing an item from the list, it shouldn't be necessary to use update. However, it's important to keep in mind that the cached data is normalized, and that Apollo utilizes the __typename and id (or _id) field to generate the cache key for any queried object. From the docs:
The InMemoryCache normalizes your data before saving it to the store
by splitting the result into individual objects, creating a unique
identifier for each object, and storing those objects in a flattened
data structure. By default, InMemoryCache will attempt to use the
commonly found primary keys of id and _id for the unique identifier if
they exist along with __typename on an object.
If the id field is missing, Apollo won't know how to match up the data from your query and the data from your mutation:
If id and _id are not specified, or if __typename is not specified,
InMemoryCache will fall back to the path to the object in the query,
such as ROOT_QUERY.allPeople.0 for the first record returned on the
allPeople root query. That would make data for given type scoped for
allPeople query and other queries would have to fetch their own
separate objects.
Fortunately, there's a workaround. You can of course just rename reviewId to id on the server side. However, if you want the cache to just use the reviewId field, you can pass in a dataIdFromObject function to your InMemoryCache constructor:
import { InMemoryCache, defaultDataIdFromObject } from 'apollo-cache-inmemory'
const cache = new InMemoryCache({
dataIdFromObject: object => {
switch (object.__typename) {
case 'Review': return object.reviewId
default: return defaultDataIdFromObject(object)
}
}
})
As long as resulting value is unique, you can utilize any other field (or combination of fields) in a similar fashion.

Resources