How to update the store after a mutation with relay? - graphql

I'm struggling to figure out how this should work. I have an application that has a current user, so, the topmost query looks like this:
query AppQuery {
currentUser {
id
email
...Account_currentUser
...AccountEdit_currentUser
}
}
AccountEdit_currentUser is defined this way:
export default createFragmentContainer(AccountEdit, {
currentUser: graphql`
fragment AccountEdit_currentUser on User {
email
name
nickName
}`
})
on the AccountEdit component. That component commits this mutation:
mutation AccountEditMutation($email: String!, $name: String!, $nickName: String!) {
updateAccount(input: {email: $email, name: $name, nickName: $nickName}) {
accountUpdated
currentUser {
id
email
name
nickName
}
}
}
After that happens, the server returns the correct values for email, name, nickName, etc. How should those new values end up in the store? Because it doesn't seem to automatic. Do I need to write a custom updater? and update config? I tried it a few times but I couldn't get anywhere close to something that even throws a reasonable error.

This updating of the store happens automatically and the reason why it wasn't is because the node_ids of the current user in the original query and on the subsequent query were different. Once I made them the same, it started working.

Related

Mutation not updating Graphcache cache entry in urql

I am working on a skin care website, and it lets you create skin care routines (Routine in type-defs) with information about how you use your skin care products (ProductUsages in type-defs).
Routine and ProductUsages are many-to-many relations. In type-defs,
type Routine {
id: ID!
# ...
productUsages: [ProductUsage!]
}
type ProductUsage {
id: ID!
# ...
routines: [Routine]
}
On the routine page, urql runs the currentRoutine query like this:
const ROUTINE_QUERY = gql`
query CurrentRoutineQuery($routineId: ID!, $ownerId: ID!) {
currentRoutine(ownerId: $ownerId) {
productUsages {
id
productId
name
brand
category {
id
label
}
frequency
rating
}
id
}
productsWithOtherUsers(routineId: $routineId)
}
`;
(only currentRoutine query is relevant but including everything here just in case)
As you can see, even though it queries a Routine, I'm more interested in ProductUsages in that routine.
Its type-def is as follows:
currentRoutine(ownerId: ID!): Routine
On the same page, users can search and submit new ProductUsages, with the following type-defs.
createProductUsageInfo(input: CreateProductUsageInfoInput): ProductUsage
I run this mutation like
const CREATE_PRODUCT_INFO_MUTATION = gql`
mutation createProductUsageInfo($input: CreateProductUsageInfoInput) {
createProductUsageInfo(input: $input) {
id
weeklyFrequency
dailyFrequency
rating
comment
routines {
id
}
}
}
`;
In the resolver, I create and return a productUsage, and include the related routines entity. Graphcache uses id as the key, so I made sure to query id for the productUsage, and for the included routines.
However, the productUsages in currentRoutine query cache, which I mentioned in the beginning, doesn't reflect the new ProductUsage entry created from this mutation. On the urql cache explorer, productUsages doesn't change.
What could I be doing wrong? I've spent so much time over the last few weeks trying to debug this.
The only thing that I can think of is that the productUsages in currentRoutines result returned from the resolver looks like productUsages: [{productUsage: {id: 1, ...}}, {productUsage: {id: 2, ...}}], so I included the following resolver under Routine to transform it like productUsages: [{id: 1, ...}, {id: 2, ...}].
async productUsages(parent) {
return parent.productUsages.map(
(productUsage) => productUsage.productUsage
);
}
Maybe it doesn't recognize the id because of this? I'm really not sure how to fix this.
It looks like you're missing __typename in the response. This is needed for URQL to match the ID to a key (Product:123). In addition to id, be sure to specify __typename. If you're using graphql-codegen, it has an option to do this automatically.
Your updated query would be:
const ROUTINE_QUERY = gql`
query CurrentRoutineQuery($routineId: ID!, $ownerId: ID!) {
currentRoutine(ownerId: $ownerId) {
productUsages {
id
productId
name
brand
category {
id
label
__typename
}
frequency
rating
__typename
}
id
__typename
}
__typename
productsWithOtherUsers(routineId: $routineId)
}
`;
Then, do the same for your mutation:
const CREATE_PRODUCT_INFO_MUTATION = gql`
mutation createProductUsageInfo($input: CreateProductUsageInfoInput) {
createProductUsageInfo(input: $input) {
id
weeklyFrequency
dailyFrequency
rating
comment
__typename
routines {
id
__typename
}
}
}
`;
Remember, only the fields you fetch in your mutation will update the cache. So in this case, your routines isn't updating any fields, since you're just fetching a list of them. But if that's intended, then that's fine.

GraphQL query error -- variable is never used in operation

I am performing a request for an individual post from Apollo Server / Express backend.
In the Apollo GraphQL sandbox, the query works and retrieves the correct post, however, the query has a red squiggle identifying an error which reads -
Variable "$getPostId" is never used in operation "Query".
The query is as follows -
query Query($getPostId: ID!) {
getPost(id:"20c9b3ac-afe6-4faa-a3f9-e00ef1b38ccf") {
title
author
id
}
}
The schema is as follows -
module.exports = gql`
type Post {
id: ID!
title: String!
author: String!
}
type Query {
getPosts: [Post]!
getPost(id: ID!): Post
}
...
`
The closest post which seems to address a similar problem I could find is here. However, I can't translate the resolution to my problem.
Why is the error showing (particularly when the query runs successfully)? What needs to be done to stop the error from showing?
Many thanks!
It sounds like
query Query($getPostId: ID!) {
getPost(id:"20c9b3ac-afe6-4faa-a3f9-e00ef1b38ccf") {
title
author
id
}
}
is supposed to be
query Query($getPostId: ID!) {
getPost(id: $getPostId) {
title
author
id
}
}
Or if your query is actually meant to hard-code the ID, then you want
query Query {
getPost(id:"20c9b3ac-afe6-4faa-a3f9-e00ef1b38ccf") {
title
author
id
}
}

Using Apollo to mix local and remote fields using the #client directive

I'm using apollo-client#2.6.10 in a VueJS application.
I have a user type that has (until recently) used local-only fields. Here is the local query and corresponding mutation:
query currentUserQuery {
user #client {
id
email
emailVerified
displayName
}
}
mutation setCurrentUser($id: ID!, $email: String!, $emailVerified: Boolean!, $displayName: String) {
update_current_user(
id: $id,
email: $email,
emailVerified: $emailVerified,
displayName: $displayName
) #client
}
And I also have a corresponding resolver for the mutation:
resolvers = {
Mutation: {
update_current_user: (_, { id, email, emailVerified, displayName }, { cache }) => {
const data = cache.readQuery({ query: currentUserQuery })
const user = {
__typename: 'User',
id,
email,
emailVerified,
displayName
}
data.user = user
cache.writeData({ data })
return user
}
}
So far, so good!
What I'd like to do now is mix in some additional fields from the remote api. So, I modified the query as such:
query currentUserQuery {
user {
id #client
email #client
emailVerified #client
displayName #client
tier {
id
name
}
}
}
I also updated the initial cache object written cache.writeQuery to include placeholder tier data.
But no matter how I try to wrangle this, the tier is always the initialised null value, and after inspecting network traffic I can see that the remote fields are not being fetched.
Should I be doing something different with resolvers? I've tried using local query resolvers for the local fields (instead of relying on direct fetches from the cache) to no avail.
After reading through the v2 docs and trying a variety of different approaches I'm at a loss to understand what's not working here.
Help greatly appreciated!

Overload GraphQL Schema to Support getting a user by username or password

I am trying to create a GraphQL Schema such that a user will be able to find a user by their email or username. Imagine for these purposes one user could have an email that is the username of another user.
My typeDefs look as follows:
const typeDefs = gql`
type Query {
user: User(username: String!)
user: User(email: String!)
}
`;
Is this a valid Schema? If not how would I change my solution to be valid and solve my problem?
Edit: So I tried to execute the above and I get an error: 'Error: Field "Query.user" can only be defined once.' As I thought I might.
Is there any way to ensure that exactly one of username and email is null in the following: user: User(username: String, email: String)?
There is no "overloading" of fields in GraphQL, and no syntax that would support one of two fields being required. An argument is either nullable or non-null, that's it. You can do this:
type Query {
user(username: String, email: String): User
}
or this
type Query {
user(filter: UserFilter!): User
}
input UserFilter {
username: String
email: String
}
and then handle validation (at least one argument is defined but not both) inside your resolver. The only other way to do it would be something like:
type Query {
user(key: UserFilterKey!, value: String!): User
}
enum UserFilterKey {
username
email
}
But this kind of approach is not very intuitive for anyone consuming the schema. It also only works if the arguments in question have the same type.

How to update apollo cache after mutation (query with filter)

I am pretty new to GraphQL. I am using graph.cool in a Vue.js project with apollo.
I am using right now the in-memory cache.
I had previously a simple 'allPosts' query.
And after creating a new one, I used the update() hook and readQuery() + writeQuery()
However I want that logged in users can only see their posts. So I modified the query with a filter.
query userStreams ($ownerId: ID!) {
allStreams(filter: {
owner: {
id: $ownerId
}
}) {
id
name
url
progress
duration
watched
owner {
id
}
}
}
My thought was, that I only need to pass in the userid variable. However this is not working. I am always getting
Error: Can't find field allStreams({"filter":{"owner":{}}}) on object (ROOT_QUERY) undefined.
this.$apollo.mutate({
mutation: CREATE_STREAM,
variables: {
name,
url,
ownerId
},
update: (store, { data: { createStream } }) => {
const data = store.readQuery({
query: USERSTREAMS,
variables: {
id: ownerId
}
})
data.allStreams.push(createStream)
store.writeQuery({
query: USER_STREAMS,
variables: {
id: ownerId
},
data
})
}
})
When you use readQuery or writeQuery, you should use the same variable name. So replace
variables: { id: ownerId }
With
variables: { ownerId }
Also, the reason you are getting an exception is that readQuery throws an exception if the data is not in the store. That happens before the first time you use writeQuery (or get the data with some other query).
You could write some default values to the store before calling this mutation.
You could also use readFragment that returns null instead of throwing an exception. But that would require more changes to your code.

Resources