Working with apollo is great but how can you change the shape of the response from a query. I am using reactjs to display user details and their music information
Say I have a query
query {
userDetails {
id,
name,
music {
name,
type
}
}
is there a way to change the shape using apollo (not in reactjs) to something like
query {
userDetails {
id,
name,
musicName,
musicType
}
}
so that I get the second shape as the response rather than the first, then which I will have to change it using reactjs
You can create local-only fields that reads from the fetched fields by using the #client-directive and TypePolicies
Assuming the typename returned from the query is "User":
Query:
query {
userDetails {
id,
name,
music {
name,
type
}
musicName#client
musicType#client
}
Client setup:
const client = new ApolloClient({
cache: new InMemoryCache({
typePolicies: {
User: {
fields: {
musicName: {
read(_, { readField }) {
return readField('music').name;
}
},
musicType: {
read(_, { readField }) {
return readField('music').type;
}
},
}
}
}
}),
uri: "yourGraphqlServer.com"
});
Related
I have a mutation to create a new card object, and I expect it should be added to the user interface after update. Cache, Apollo Chrome tool, and console logging reflect the changes, but the UI does not without a manual reload.
const [createCard, { loading, error }] = useMutation(CREATE_CARD, {
update(cache, { data: { createCard } }) {
let localData = cache.readQuery({
query: CARDS_QUERY,
variables: { id: deckId }
});
localData.deck.cards = [...localData.deck.cards, createCard];
;
client.writeQuery({
query: CARDS_QUERY,
variables: { id: parseInt(localData.deck.id, 10) },
data: { ...localData }
});
I have changed cache.writeQuery to client.writeQuery, but that didn't solve the problem.
For reference, here is the Query I am running...
const CARDS_QUERY = gql`
query CardsQuery($id: ID!) {
deck(id: $id) {
id
deckName
user {
id
}
cards {
id
front
back
pictureName
pictureUrl
createdAt
}
}
toggleDeleteSuccess #client
}
`;
I managed the same result without the cloneDeep method. Just using the spread operator solved my problem.
const update = (cache, {data}) => {
const queryData = cache.readQuery({query: USER_QUERY})
const cartItemId = data.cartItem.id
queryData.me.cart = queryData.me.cart.filter(v => v.id !== cartItemId)
cache.writeQuery({query: USER_QUERY, data: {...queryData}})
}
Hope this helps someone else.
Ok, finally ran into a long Github thread discussing their solutions for the same issue. The solution that ultimately worked for me was deep cloning the data object (I personally used Lodash cloneDeep), which after passing in the mutated data object to cache.writeQuery, it was finally updating the UI. Ultimately, it still seems like there ought to be a way to trigger the UI update, considering the cache reflects the changes.
Here's the after, view my original question for the before...
const [createCard, { loading, error }] = useMutation(CREATE_CARD, {
update(cache, { data: { createCard } }) {
const localData = cloneDeep( // Lodash cloneDeep to make a fresh object
cache.readQuery({
query: CARDS_QUERY,
variables: { id: deckId }
})
);
localData.deck.cards = [...localData.deck.cards, createCard]; //Push the mutation to the object
cache.writeQuery({
query: CARDS_QUERY,
variables: { id: localData.deck.id },
data: { ...localData } // Cloning ultimately triggers the UI update since writeQuery now sees a new object.
});
},
});
I’m trying to develop a spring boot graphQl service using graphql-java-8 library. I’m fetching data from a web-service, the response I get from the service is a bit like dynamic for which I have to introduce graphQl interface in my response graphQl schema.
extend type Query {
search(
name: String,
category: String
): [ResultOne]
}
interface ResultOne {
name: String
content: String
}
type Fish implements ResultOne {
name: String
content: String
weight: Float
}
type Fruit implements ResultOne {
name: String
content: String
Color: String
}
type Toy implements ResultOne {
name: String
content: String
description: String
}
To wiring my model with graphQl framework,
return RuntimeWiring.newRuntimeWiring()
.wiringFactory(new WiringFactory() {})
.type(
TypeRuntimeWiring.newTypeWiring("ResultOne")
.typeResolver(env -> {
if(env.getObject() instanceof Map) {
Map object = env.getObject();
if (object.containsKey("name") && object.get("name").equals("fish")) {
return (GraphQLObjectType) env.getSchema().getType("Fish");
} else if (object.containsKey("name") && object.get("name").equals("fruit")) {
return (GraphQLObjectType) env.getSchema().getType("Fruit");
} else if(object.containsKey("name") && object.get("name").equals("toy")) {
return (GraphQLObjectType) env.getSchema().getType("Toy");
} else {
return null;
}
} else {
return null;
}
})
)
.build();
So, type resolving issue is also fix a way, not sure it’s ideal or not. For data binding I’m not sure how do I do that in graphQl’s recommended way. I would like to add that, I’ve a single End-Point and single fetcher for the whole API. Data are fetched in a single request and don't want to call twice as I already have whole response. I had to resolve the type at runtime and wire the data for implemented model. So far data are fetched perfectly and the values are also coming till the interface against my query, but appeared null for interface implemented model part e.g: Fish, Fruit & Toy in this example. My question is how do I manupulate dynamically resolved type data for the java library?
Feel free to ask me any question regarding this issue.
Sample query:
{
search() {
ResultOne {
name
content
... on Fish {
weight
}
}
}
}
Corresponding response that I'm currently getting:
{
"data": {
"search": [
{
"resultOne": [
{
"name": "Salmon",
"content": "Frozen Food",
"weight": null
}
]
}
]
},
"extensions": {
"Total-ResponseTime": 23020,
"resultOne-Time": 22683
}
}
I created this question in case anyone was curious on how to add union / Polymorphic types in Apollo. Hopefully this will make it easier for them.
In this example I wanted the response to either be a Worksheet or ApiError
// typedefs.js
export default [`
schema {
query: Query
}
type Query {
worksheet(id: String!): Worksheet | Error
}
type Worksheet {
id: String!
name String
}
type ApiError {
code: String!
message: String!
}
`];
// resolvers.js
export default {
Query: {
worksheet(_, args, { loaders }) {
return loaders.worksheet.get(args.id).catch(() => {
// ApiError
return {
code: '1',
message: 'test'
}
});
}
}
};
// Express Server
import { graphqlExpress } from 'apollo-server-express';
import { makeExecutableSchema } from 'graphql-tools';
import typeDefs from './typedefs';
import resolvers from './resolvers';
...
app.post(
'/graphql',
graphqlExpress(req => ({
makeExecutableSchema({ typeDefs, resolvers }),
context: mkRequestContext(req.ctx, req.log),
formatError: formatGraphQLError(req.ctx, req.log)
}))
);
In GraphQL to add a union type in the typedefs you have to define the union
i.e union WorksheetOrError = Worksheet | ApiError
// typedefs.js
export default [
`
schema {
query: Query
}
type Query {
worksheet(id: String!): WorksheetOrError
}
union WorksheetOrError = Worksheet | ApiError
type Worksheet {
id: String!
name String
}
type ApiError {
code: String!
message: String!
}
`];
In the resolvers you have to define a resolver for the union type that has the property __resolveType. This will help tell the GraphQL executor which type the result is.
// resolvers.js
export default {
Query: {
worksheet() {
...
}
},
WorksheetOrError: {
__resolveType(obj) {
if (obj.id) {
return 'Worksheet';
}
if (obj.code) {
return 'ApiError';
}
return null;
}
},
};
To create a GraphQL Query in Apollo Client
// Your application code.
// This is my Worksheet Query in my React code.
const WorksheetQuery = gql`
query GetWorksheet($worksheetId: String!) {
worksheet(id: $worksheetId) {
... on Worksheet {
id
name
}
... on ApiError {
code
message
}
}
}
Now you can check the __typename to check what type is in the response.
Note: For those who are wondering why I'm not using GraphQL errors. It's because Apollo doesn't seem to handle errors well when it encounters a graphQL error. So for a work around I'm trying to return a custom ApiError in my response.
There a few reasons why using a union with an error type is nice.
Currently if you wanted a partial response with GraphQLError. Apollo does not cache the errors so if you wanted to re-use the cached response later you wouldn't have the complete response since the errors are removed. (Now you can't display the proper UI with errors)
Getting GraphQLError back in Apollo would return a flat list of errors with the path to where the error is in the data. So you would need to verify that which part of your schema did the error occur in. However if you follow the instructions above you would have the error within the schema already. That way you already know which part of the schema the error happened.
I have two loopback model like
Model1
{
"id":"",
"name":"",
"model2Ids":[]
}
This model has an array of model 2 ids using the referencesMany relation.
I want to query Model from the remote method of model 1.
My remote method is as follows
Model1.customRemoteMethod = function(id, cb) {
var Model2 = Model1.app.models.model2;
Model1.findById(id, function(model1Error, model1Obj) {
if (model1Error) cb(model1Error);
if (model1Error.model2Ids) {
var model2Filter = {
'where': {
'id': {'inq': model1Obj.model2Ids}
}
};
Model1.find(model2Filter, function(model2Error, model2s) {
if (model2Error) cb(model2Error);
cb(null, Object.assign({}, JSON.parse(JSON.stringify((channelMapObj))), {
model2s: model2s
}));
});
}
})
})
I want the models that are returned(that is the filtered results) to be in the same array as that of my ids in Model1.Is there a way to do it.?
For example if I query using id 2,1,3.
Then the results should be the same order and not 1,2,3(as found in model 2).
The only solution that I have would be to loop over the id, and the use findById to get all results in same order.
I have two models. Company and Booking.
In the relation part of model Company
"bookings": {
"type": "hasMany",
"model": "Booking",
"foreignKey": "companyId"
}
The issue is, it is possible to post a Booking without companyId which is not ok.
From the explorer.
Booking {
bookingId (string, optional),
name (string),
location (string),
companyId (string, optional)
}
You actually can't enforce this particular validation out-of-the-box. Instead you have a couple different options:
a) You can enforce the creation of Booking through company through the endpoint, POST /api/Company/{id}/Bookings by disabling Booking.disableRemoteMethod('create', ...) along with any other methods that have the ability to create records from the Booking model.
b) You can add a remote hook to check whether or not the company record exists and throw an error accordingly.
Booking.beforeRemote('create', function(ctx, booking, next) {
var Company = Booking.app.models.Company;
var companyId = ctx.req.body.companyId;
if (companyId) {
errorMsg = 'Company with id=' + companyId + ' does not exist.';
var noCompany = new Error(errorMsg);
Company.findById(companyId, function(err, company) {
if (err) next(err);
if (!company) {
ctx.res.statusCode = 400;
next(noCompany);
} else {
next();
}
});
} else {
next();
}
});
You will also have to do the same thing for any other endpoints that allow record create such as PUT /api/Bookings.
Hope that helps!
You can add the companyId property explicitly in the booking.json file, and add the required:true property. It would look like
{
"properties": {
"companyId": {
"type": "number",
"required": true
},
... //other properties
}
}
In richardpringle's answer part b booking was an empty object for me.
I was able to get this answer to work by adding app to the booking.js file like below:
'use strict';
var app = require('../../server/server');
module.exports = function(Booking) {
Booking.beforeRemote('create', function(ctx, booking, next) {
var Company = app.models.Company;
var companyId = ctx.req.body.companyId;
if (companyId) {
errorMsg = 'Company with id=' + companyId + ' does not exist.';
var noCompany = new Error(errorMsg);
Company.findById(companyId, function(err, company) {
if (err) next(err);
if (!company) {
ctx.res.statusCode = 400;
next(noCompany);
} else {
next();
}
});
} else {
next();
}
});
};
line 2 is new
line 6 is modified from richardpringle's answer
From: var Company = Booking.app.models.Company;
To: var Company = app.models.Company;