RN Apollo Client 3.0 - handle refetch with merge function - react-apollo

I am recently migrating to apollo client 3.0 from 2.0.
I have a query that requires fetch more and pagination.
By doing,
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
getData: {
// Handles incoming data
keyArgs: [],
merge(existing ={/*some default object fields*/}, incoming) {
return {
...existing,
pageInfo: incoming.pageInfo,
edges: [...existing.edges, ...incoming.edges],
};
},
},
},
},
},
});
I was able to handle both initial query/fetch and pagination.
However, I am having trouble with how to handle refetch.
With this merge function, refetched data get just concatenated with existing cache data.
I am not able to find how to correctly handle this in merge function.
If anyone know how to handle this, please let me know.

I was able to work around by observing args.
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
getData: {
// Handles incoming data
keyArgs: [],
merge(existing ={/*some default object fields*/}, incoming, {args}) {
if(args && !args.after){
// Initial fetch or refetch
return incoming;
}
// Pagination
return {
...existing,
pageInfo: incoming.pageInfo,
edges: [...existing.edges, ...incoming.edges],
};
},
},
},
},
},
});

Related

Strapi custom service overwrite find method

I'm using strapi v4 and I want to populate all nested fields by default when I retrieve a list of my objects (contact-infos). Therefore I have overwritten the contact-info service with following code:
export default factories.createCoreService('api::contact-info.contact-info', ({ strapi }): {} => ({
async find(...args) {
let { results, pagination } = await super.find(...args)
results = await strapi.entityService.findMany('api::contact-info.contact-info', {
fields: ['locale'],
populate: {
sections: {
populate: { link: true }
}
}
})
return { results, pagination }
},
}));
That works well, but I execute a find all entries on the database twice, I guess, which I want to avoid, but when I try to return the result from the entityService directly I'm getting following response:
data": null,
"error": {
"status": 404,
"name": "NotFoundError",
"message": "Not Found",
"details": {}
}
also, I have no idea how I would retrieve the pagination information if I don't call super.find(). Is there any way to find all contents with the option to populate nested objects?
the recommended way of doing this, would be a middleware (do it once apply for all controllers). There would be an video Best Practice Session 003 where it's describes exactly this scenario (Not sure if it's discord only, but on moment of writing this it wasn't yet published).
So regarding rest of your question:
async find(...args) {
let { results, pagination } = await super.find({...args, populate: {section: ['link']})
}
should be sufficient to fix that up in one query
custom pagination example:
async findOne(ctx) {
const { user, auth } = ctx.state;
const { id } = ctx.params;
const limit = ctx.query?.limit ?? 20;
const offset = ctx.query?.offset ?? 0;
const logs = await strapi.db.query("api::tasks-log.tasks-log").findMany({
where: { task: id },
limit,
offset,
orderBy: { updatedAt: "DESC" },
});
const total = await strapi.db
.query("api::tasks-log.tasks-log")
.count({ where: { task: id } });
return { data: logs, meta: { total, offset, limit } };
}
one small addition to the accepted answer, the answer didn't work completely since args is an array with an object inside, so I had to do it like this:
async find(...args) {
const argsObj = args[0]
let { results, pagination } = await super.find({...argsObj, populate: {section: ['link']})
}

Apollo cache fetchMore & mutation

I'm trying to create a page with a load more and with a button that execute a mutation against a particular item of the list, this is the query:
const { data, fetchMore, networkStatus } =
useSearchItemsQuery({
notifyOnNetworkStatusChange: true,
variables,
});
this is the fetchMore:
fetchMore({
variables: {
offset: data.searchItems.nodes.length,
},
});
this is my apollo config:
return new ApolloClient({
...
cache: new InMemoryCache({
typePolicies: {
Item: {
keyFields: ["uuid"],
merge: true,
},
Query: {
fields: {
searchItems: {
keyArgs: false,
merge(existing = [], incoming, { args }) {
console.log("options", args);
return deepmerge(existing, incoming, {
arrayMerge: (destinationArray, sourceArray) => {
const refs = [...destinationArray, ...sourceArray].map(
(o) => o.__ref
);
const array = [...destinationArray, ...sourceArray].filter(
({ __ref }, index) => !refs.includes(__ref, index + 1)
);
console.log("array", array);
return array;
},
});
},
},
},
},
},
}),
});
};
the fetchMore works correctly but when I try to run a mutation I can see the changes on the screen but the searchItems query is been refetched and the list on the screen update with the initial state, before the fetchMore.
Example:
open the page and 1 item appear
click fetchMore
a new item appear
run a mutation against the 1st item or the 2nd item
the list now contains only the 1st item
I can see on the network tab the after the mutation the initial query it's been executed with the initial offset/limit

GraphQL Stitching - Why would fields from subschemas return null?

I'm attempting to stitch together two GraphQL schemas, one from contentful and one from neo4j.
Each subschema appears to be interrogated during queries across the combined schema but "foreign" fields always come back as null.
I just can't figure this one out.
Sample Query:
query {
//Request data exclusively from the neo4j schema
Product(id:"475e006f-b9cf-4f40-8712-271ceb46d14b"){
id,
name,
weight
},
//This is a contentful schema query which should return weight from neo4j
product(id:"[contentful-native-id]"){
id,
weight,
}
}
Result:
"data": {
"Product": [
{
"id": "475e006f-b9cf-4f40-8712-271ceb46d14b",
"name": "Test product name",
"weight": 14.9
}
],
"product": {
"id": "475e006f-b9cf-4f40-8712-271ceb46d14b",
"weight": null //This shouldn't be null! :(
}
}
Logging:
//First query being executed against neo4j database
neo4j-graphql-js MATCH (`product`:`Product` {id:$id}) RETURN `product` { .id , .name , .weight } AS `product`
neo4j-graphql-js {
"offset": 0,
"first": -1,
"id": "475e006f-b9cf-4f40-8712-271ceb46d14b"
}
//Triggered by the second query correctly trying to resolve weight from neo4j
neo4j-graphql-js MATCH (`product`:`Product` {id:$id}) RETURN `product` { .weight , .id } AS `product`
neo4j-graphql-js {
"offset": 0,
"first": -1,
"id": "475e006f-b9cf-4f40-8712-271ceb46d14b"
}
This seems to suggest something is working, but the result of weight never makes it to the final output.
ApolloServer doesn't report any errors via didEncounterErrors()
Stitching:
const gatewaySchema = stitchSchemas({
subschemas: [{
schema: neoSchema,
merge: {
Product: {
selectionSet: '{id}',
fieldName: 'Product',
args: ({
id
}) => ({
id
}),
}
}
},
{
schema: contentfulSchema,
merge: {
}
}
],
})
Schemas:
const executor = async ({
document,
variables,
context
}) => {
const query = print(document);
//console.log(query);
const fetchResult = await fetch('https://graphql.contentful.com/content/v1/spaces/[SPACE]', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer [AUTHTOKEN]`,
},
body: JSON.stringify({
query,
variables
})
});
return fetchResult.json();
};
const contentfulSchema = wrapSchema({
schema: await introspectSchema(executor),
executor: executor
});
const driver = neo4j.driver(
process.env.NEO4J_URI || 'bolt://localhost:7687',
neo4j.auth.basic(
process.env.NEO4J_USER,
process.env.NEO4J_PASS
), {
encrypted: process.env.NEO4J_ENCRYPTED ? 'ENCRYPTION_ON' : 'ENCRYPTION_OFF',
}
)
const neoSchema = makeAugmentedSchema({
typeDefs: typeDefs,
});
Server:
const server = new ApolloServer({
schema: gatewaySchema,
context: ({ req }) => {
return {
driver,
req
};
},
plugins:[
myPlugin
]
});
Any insight or ideas much appreciated!
This appears to be down to the fact that stitchSchemas is NOT supported in ApolloServer...
Does Apollo Server work with GraphQL Tools stitchSchemas?

I need help understanding Relay OutputFields, getFatQuery

This is the code from official docs of relay, This is for GraphQLAddTodoMutation
const GraphQLAddTodoMutation = mutationWithClientMutationId({
name: 'AddTodo',
inputFields: {
text: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
todoEdge: {
type: GraphQLTodoEdge,
resolve: ({localTodoId}) => {
const todo = getTodo(localTodoId);
return {
cursor: cursorForObjectInConnection(getTodos(), todo),
node: todo,
};
},
},
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
},
mutateAndGetPayload: ({text}) => {
const localTodoId = addTodo(text);
return {localTodoId};
},
});
I think mutateAndGetPayload executes first then outputFields? since it used localTodoId object as parameter, I see localTodoId object returned from mutateAndGetPayload.
and this is the code for relay mutation.please look at the getFatQuery
export default class AddTodoMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`
fragment on User {
id,
totalCount,
}
`,
};
getMutation() {
return Relay.QL`mutation{addTodo}`;
}
getFatQuery() {
return Relay.QL`
fragment on AddTodoPayload #relay(pattern: true) {
todoEdge,
viewer {
todos,
totalCount,
},
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'todos',
edgeName: 'todoEdge',
rangeBehaviors: ({status}) => {
if (status === 'completed') {
return 'ignore';
} else {
return 'append';
}
},
}];
}
getVariables() {
return {
text: this.props.text,
};
}
getOptimisticResponse() {
return {
// FIXME: totalCount gets updated optimistically, but this edge does not
// get added until the server responds
todoEdge: {
node: {
complete: false,
text: this.props.text,
},
},
viewer: {
id: this.props.viewer.id,
totalCount: this.props.viewer.totalCount + 1,
},
};
}
}
I think the todoEdge is from the outputFields from GraphQL? I see a viewer query on it, why does it need to query the viewer? How do I create a getFatQuery? I would really appreciate if someone help me understand this more and about Relay mutation.
mutateAndGetPayload executes then returns the payload to the outputFields
mutationWithClientMutationId
Source-Code
starWarsSchema example
mutationWithClientMutationId
inputFields: defines the input structures for mutation, where the input fields will be wraped with the input values
outputFields: defines the ouptput structure of the fields after the mutation is done which we can view and read
mutateAndGetPayload: this function is the core one to relay mutations, which performs the mutaion logic (such as database operations) and will return the payload to be exposed to output fields of the mutation.
mutateAndGetPayload maps from the input fields to the output fields using the mutation
operation. The first argument it receives is the list of the input parameters, which we can read to perform the mutation action
The object we return from mutateAndGetPayload can be accessed within the output fields
resolve() functions as the first argument.
getFatQuery() is where we represent, using a GraphQL fragment, everything
in our data model that could change as a result of this mutation

GraphQL how to mutate data

I have a basic schema for mutating some data which looks like
const schema = new graphql.GraphQLSchema({
mutation: new graphql.GraphQLObjectType({
name: 'Remove',
fields: {
removeUser: {
type: userType,
args: {
id: { type: graphql.GraphQLString }
},
resolve(_, args) {
const removedData = data[args.id];
delete data[args.id];
return removedData;
},
},
},
})
});
Looking around google I cant find a clear example of the example query which needs to be sent to mutate.
I have tried
POST -
localhost:3000/graphql?query={removeUser(id:"1"){id, name}}
This fails with error:
{
"errors": [
{
"message": "Cannot query field \"removeUser\" on type \"Query\".",
"locations": [
{
"line": 1,
"column": 2
}
]
}
]
}
In order to post requests from the front-end application it is recommended to use apollo-client package. Say i wanted to validate a user login information:
import gql from 'graphql-tag';
import ApolloClient, {createNetworkInterface} from 'apollo-client';
client = new ApolloClient({
networkInterface: createNetworkInterface('http://localhost:3000/graphql')
});
remove(){
client.mutate({
mutation: gql`
mutation remove(
$id: String!
) {
removeUser(
id: $id
){
id,
name
}
}
`,
variables: {
id: "1"
}
}).then((graphQLResult)=> {
const { errors, data } = graphQLResult;
if(!errors && data){
console.log('removed successfully ' + data.id + ' ' + data.name);
}else{
console.log('failed to remove');
}
})
}
More information about apollo-client can be found here
Have you tried using graphiql to query and mutate your schema?
If you'd like to create a POST request manually you might wanna try to struct it in the right form:
?query=mutation{removeUser(id:"1"){id, name}}
(Haven't tried POSTing myself, let me know if you succeeded, i structured this out of the url when using graphiql)
You have to explicitly label your mutation as such, i.e.
mutation {
removeUser(id: "1"){
id,
name
}
}
In GraphQL, if you leave out the mutation keyword, it's just a shorthand for sending a query, i.e. the execution engine will interpret it as
query {
removeUser(id: "1"){
id,
name
}
}
cf. Section 2.3 of the GraphQL Specification
const client = require("../common/gqlClient")();
const {
createContestParticipants,
} = require("../common/queriesAndMutations");
const gql = require("graphql-tag");
const createPartpantGql = async (predictObj) => {
try {
let resp = await client.mutate({
mutation: gql(createContestParticipants),
variables: {
input: {
...predictObj,
},
},
});
let contestParticipantResp = resp.data.createContestParticipants;
return {
success: true,
data: contestParticipantResp,
};
} catch (err) {
console.log(err.message)
console.error(`Error creating the contest`);
return {
success: false,
message: JSON.stringify(err.message),
};
}
};

Resources