With GraphQL + Apollo Client, how to return a totalCount after mutating? - reactjs

I have the following:
const typeDefs = `
type Request {
id: ID!
email: String!
totalCount: Int
}
type Mutation {
createRequest(email: String!): Request!
}
`;
export default typeDefs;
Right now totalCount is returning null. Using GraphQL, what is the right way to return the TotalCount as the mutation's response. Should totalCount be included somehow in the Request model?

You've defined totalCount as a property of each Request object but totalCount represents the count of all Request objects.
The common practice to allow additional data in mutation response is to have it return a wrapper. For example:
type Mutation {
createRequest(email: String!): CreateRequestResponse!
}
type CreateRequestResponse {
request: Request!
totalCount: Int!
}
The CreateRequestResponse is a wrapper object that can include any arbitrary attributes that the clients might need in the response. totalCount here is just an example; you can add any attribute there.
With that, the definition of Request type would be:
type Request {
id: ID!
email: String!
}
which is ideal because it only contains attributes of a specific Request.

Related

Hotchocolate Strawberryshake how to use a mutation?

There seem to be no examples at all on how to do a mutation by a graphql client using Strawberryshake (C#). Using Strawberryshake Version11.0.0-preview.138, Abstractions and Http v. 10.3.0-preview.12 and CodeGeneration.CSharp.Analyzers v. 0.0.30, I can run the query from the example just fine. Now I added a mutation, but I can't figure how to use it?
I got the following from the schema import (Alarm.graphql):
schema {
query: Query
mutation: Mutation
}
type Query {
alarms: [Alarm]
}
type Mutation {
addAlarm(input: AlarmInput): AlarmPayload
}
type AlarmPayload {
alarm: Alarm
}
type Alarm {
id: Int!
handle: Int!
messageId: Int!
message: String
}
input AlarmInput {
id: Int!
handle: Int!
messageId: Int!
message: String
}
The query using
query getAlarms {
alarms {
message
}
}
is just fine. For the mutation I added
mutation writeAlarm {
addAlarm {
alarm {
id
handle
messageId
message
}
}
}
Which successfully generates quite a lot of sensible looking classes on build. Now I create a connection to the server:
var serviceCollection = new ServiceCollection();
serviceCollection.AddHttpClient(
"ConnectorLogGraphQL",
c => c.BaseAddress = new Uri("http://localhost:5000/graphql"));
serviceCollection.AddAlarmClient();
var services = serviceCollection.BuildServiceProvider();
IAlarmClient client = services.GetRequiredService<IAlarmClient>();
The client is what I need to use. For the query, I can use:
var result = await client.GetAlarmsAsync();
similarly
var result = await client.WriteAlarmAsync();
compiles just fine, but of course it doesn't do what I'd like to. There's some WriteAlarmOperation defined, which I could pass as a parameter to WriteAlarmAsync, but this operation doesn't have fields for the input either. Something's missing.. ?

ReactJs Check Validity of HTTP Request Response

I am wondering if there is a way to check if the response type is parsable into a specific type:
interface ExpectedResponse
{
anInteger: number;
aBoolean: bool;
aString: string;
}
function sendRequest() : Promise<ExpectedRespnse>
{
const response = await <HTTP call>
return response as ExpectedResponse;
}
In this example, if the response data can be mapped 1:1 to the interface, everything is fine. The object will be treated as an ExpectedResponse object when automatically deserialized.
Is there a way to ensure that the response data is ExpectedResponse with all required attributes filled and no "undefined" attributes not filled?

React Apollo updating client cache after mutation

I am trying to update my chache after succesfully executing a mutation. Here is my query and mutation:
export const Dojo_QUERY = gql`
query Dojo($id: Int!){
dojo(id: $id){
id,
name,
logoUrl,
location {
id,
city,
country
},
members{
id
},
disziplines{
id,
name
}
}
}`;
export const addDiszipline_MUTATION = gql`
mutation createDisziplin($input:DisziplineInput!,$dojoId:Int!){
createDisziplin(input:$input,dojoId:$dojoId){
disziplin{
name,
id
}
}
}`;
and my mutation call:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { disziplines } = cache.readQuery({ query: Constants.Dojo_QUERY,variables: {id}});
console.log(disziplines)
cache.writeQuery({
...some update logic (craches in line above)
});
}
}
);
when i execute this mutation i get the error
Invariant Violation: "Can't find field dojo({"id":1}) on object {
"dojo({\"id\":\"1\"})": {
"type": "id",
"generated": false,
"id": "DojoType:1",
"typename": "DojoType"
}
}."
In my client cache i can see
data{data{DojoType {...WITH ALL DATA INSIDE APPART FROM THE NEW DISZIPLINE}}
and
data{data{DisziplineType {THE NEW OBJECT}}
There seems to be a lot of confusion around the client cache around the web. Somehow none of the posed solutions helped, or made any sense to me. Any help would be greatly appreciated.
EDIT 1:
Maybe this can help?
ROOT_QUERY: {…}
"dojo({\"id\":\"1\"})": {…}​​​​​
generated: false​​​​​
id: "DojoType:1"​​​​​
type: "id"​​​​​
typename: "DojoType"​​​​​
<prototype>: Object { … }​​​​
<prototype>: Object { … }
Edit 2
I have taken Herku advice and started using fragment. however it still seems to not quite work.
My udated code:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { dojo } = cache.readFragment(
{ fragment: Constants.Diszilines_FRAGMENT,
id:"DojoType:"+id.toString()});
console.log(dojo)
}
}
);
with
export const Diszilines_FRAGMENT=gql`
fragment currentDojo on Dojo{
id,
name,
disziplines{
id,
name
}
}
`;
however the result from console.log(dojo) is still undefined.Any advice?
So I think your actual error is that you have to supply the ID as as a string: variables: {id: id.toString()}. You can see that these two lines are different:
dojo({\"id\":1})
dojo({\"id\":\"1\"})
But I would highly suggest to use readFragment instead of readQuery and update the dojo with the ID supplied. This should update the query as well and all other occurrences of the dojo in all your queries. You can find documentation on readFragment here.
Another trick is as well to simply return the whole dojo in the response of the mutation. I would say people should be less afraid of that and not do to much cache updates because cache updates are implicit behaviour of your API that is nowhere in your type system. That the new disziplin can be found in the disziplins field is now encoded in your frontend. Imagine you want to add another step here where new disziplins have to be approved first before they end up in there. If the mutation returns the whole dojo a simple backend change would do the job and your clients don't have to be aware of that behaviour.

Self reference type in GraphQL [duplicate]

Hi I am trying to learn GraphQL language. I have below snippet of code.
// Welcome to Launchpad!
// Log in to edit and save pads, run queries in GraphiQL on the right.
// Click "Download" above to get a zip with a standalone Node.js server.
// See docs and examples at https://github.com/apollographql/awesome-launchpad
// graphql-tools combines a schema string with resolvers.
import { makeExecutableSchema } from 'graphql-tools';
// Construct a schema, using GraphQL schema language
const typeDefs = `
type User {
name: String!
age: Int!
}
type Query {
me: User
}
`;
const user = { name: 'Williams', age: 26};
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
me: (root, args, context) => {
return user;
},
},
};
// Required: Export the GraphQL.js schema object as "schema"
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
// Optional: Export a function to get context from the request. It accepts two
// parameters - headers (lowercased http headers) and secrets (secrets defined
// in secrets section). It must return an object (or a promise resolving to it).
export function context(headers, secrets) {
return {
headers,
secrets,
};
};
// Optional: Export a root value to be passed during execution
// export const rootValue = {};
// Optional: Export a root function, that returns root to be passed
// during execution, accepting headers and secrets. It can return a
// promise. rootFunction takes precedence over rootValue.
// export function rootFunction(headers, secrets) {
// return {
// headers,
// secrets,
// };
// };
Request:
{
me
}
Response:
{
"errors": [
{
"message": "Field \"me\" of type \"User\" must have a selection of subfields. Did you mean \"me { ... }\"?",
"locations": [
{
"line": 4,
"column": 3
}
]
}
]
}
Does anyone know what I am doing wrong ? How to fix it ?
From the docs:
A GraphQL object type has a name and fields, but at some point those
fields have to resolve to some concrete data. That's where the scalar
types come in: they represent the leaves of the query.
GraphQL requires that you construct your queries in a way that only returns concrete data. Each field has to ultimately resolve to one or more scalars (or enums). That means you cannot just request a field that resolves to a type without also indicating which fields of that type you want to get back.
That's what the error message you received is telling you -- you requested a User type, but you didn't tell GraphQL at least one field to get back from that type.
To fix it, just change your request to include name like this:
{
me {
name
}
}
... or age. Or both. You cannot, however, request a specific type and expect GraphQL to provide all the fields for it -- you will always have to provide a selection (one or more) of fields for that type.

Add filtering option to the graphql paginated query

I am using this nice apollo-universal-starter-kit in one of my projects. I have a task to add a filtering option to this page to filter posts that have more than 2 comments.
The starter kit uses Apollo graphql-server as the back-end. The schema description for the posts looks like this:
# Post
type Post {
id: Int!
title: String!
content: String!
comments: [Comment]
}
# Comment
type Comment {
id: Int!
content: String!
}
# Edges for PostsQuery
type PostEdges {
node: Post
cursor: Int
}
# PageInfo for PostsQuery
type PostPageInfo {
endCursor: Int
hasNextPage: Boolean
}
# Posts relay-style pagination query
type PostsQuery {
totalCount: Int
edges: [PostEdges]
pageInfo: PostPageInfo
}
extend type Query {
# Posts pagination query
postsQuery(limit: Int, after: Int): PostsQuery
# Post
post(id: Int!): Post
}
postsQuery is used to generate a paginated result of the posts
Here is how postsQuery resolves (complete code here)
async postsQuery(obj, { limit, after }, context) {
let edgesArray = [];
let posts = await context.Post.getPostsPagination(limit, after);
posts.map(post => {
edgesArray.push({
cursor: post.id,
node: {
id: post.id,
title: post.title,
content: post.content,
}
});
});
let endCursor = edgesArray.length > 0 ? edgesArray[edgesArray.length - 1].cursor : 0;
let values = await Promise.all([context.Post.getTotal(), context.Post.getNextPageFlag(endCursor)]);
return {
totalCount: values[0].count,
edges: edgesArray,
pageInfo: {
endCursor: endCursor,
hasNextPage: values[1].count > 0
}
};
}
And, here is a graphql query which is used on the front-end with React post_list component (complete code for the component is here)
query getPosts($limit: Int!, $after: ID) {
postsQuery(limit: $limit, after: $after) {
totalCount
edges {
cursor
node {
... PostInfo
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
This was a long introduction :-), sorry
Question:
How can I add filtering option to the post_list component/page? I kind of understand the React side of the question, but I do not understand the graphql one. Should I add a new variable to the postsQuery(limit: $limit, after: $after) so it looks like postsQuery(limit: $limit, after: $after, numberOfComments: $numberOfComments)? And then somehow resolve it on the back-end? Or, I am on the wrong track and should think in the different direction? If so, can you point me to the right direction? :-)
Thank you in advance!
IMO I would definitely solve this on the backend. As far as whether or not it should be a new variable, i personally would say yes, and your Post.getPostsPagination could be updated to support filtering on comment count, ideally you want that as part of the DB request so that you know you are getting N number of the types of posts you want, otherwise it will open up the door for edgecases in your pagination.
The other option is a new query for CuratedPosts, or FilteredPosts, or whatever you want to call it that already knows to filter based on number of comments.

Resources