I'm trying to create entities with relational id's. Checked the normalizr example with relational data, but when paginating, old array of post id's are replaced with new array of post id's, they're not merged.
initial entity:
const entities = {
users: {
testUser: {
posts: ['1', '2']
}
}
};
what happens after pagination:
const entities = {
users: {
testUser: {
posts: ['3', '4']
}
}
};
What I expect after pagination:
const entities = {
users: {
testUser: {
posts: ['1', '2', '3' ,'4']
}
}
};
This is code, am I missing something ? Thank you !
const processStrategy = (value, parent, key) => {
switch (key) {
case 'author':
return { ...value, posts: [parent.id] };
default:
return { ...value };
}
};
const mergeStrategy = (entityA, entityB) => {
return {
...entityA,
...entityB,
posts: [...(entityA.posts || []), ...(entityB.posts || [])],
};
};
const user = new schema.Entity(
'users',
{},
{
idAttribute: 'username',
mergeStrategy,
processStrategy
}
);
Related
fragment commentFragment on Comment {
id
text
galleryId
commentUser {
id
firstName
lastName
}
}
fragment galleryFragment on Gallery {
id
path
label
comments {
...commentFragment
}
}
We first retrieve the getGalleries using the following gql :
query getGalleries($filters: galleryFilterInput) {
getGalleries(filters: $filters) {
galleries {
...galleryFragment
}
cursor
hasMore
}
}
Now when the user enters a comment on a single gallery item we run the following mutation :
mutation addCommentMutation($input: addCommentInput!) {
addComment(input: $input) {
...commentFragment
}
}
Now, we were previously using refetchQueries to update the Galleries but we have now decided to use cache.modify however we are having problem with updating the galleries
update: (cache, data: any) => {
cache.modify({
fields: {
getGalleries(existing, { readField }) {
const comment = data.data.addComment;
const newEventRef = cache.writeFragment({
fragment: commentFragment,
data: comment,
fragmentName: "commentFragment",
});
const index = existing.galleries.findIndex(
aGallery => aGallery.id === comment.galleryId
);
if (index !== -1) {
const existingCommentRef = readField("comments", existing.galleries[index])
as readonly Reference;
const newCommentsRefs = [...existingCommentRef, newRef];
cache.writeFragment({
id: "Gallery:" + readField("id", existing.galleries[index]),
fragment: gql`
fragment comments on Gallery {
comments {
...commentFragment
}
}
`,
data: newCommentsRefs,
});
}
return existing;
},
},
});
},
I am unsure how I update the newCommentsRefs in that Gallery
update(cache, data) {
const comment = data.data.addComment;
cache.writeFragment({
fragment: commentFragment,
data: comment,
fragmentName: "commentFragment",
});
const gallery: Gallery = cache.readFragment({
id: `Gallery:${comment.galleryId}`,
fragment: galleryFragment,
fragmentName: 'galleryFragment'
});
if (gallery) {
const newComments = [...gallery.comments, comment]
cache.writeFragment({
id: `Gallery:${comment.galleryId}`,
fragment: galleryFragment,
fragmentName: 'galleryFragment',
data: { ...gallery, comments: newComments }
});
}
}
I'd love to implement nested pagination within my application. I have been reading the docs and looking at several other examples but I just can't get this to work - any help is appreciated! Thanks!
React component:
I am clicking the button to run the fetchMore function provided by the useQuery hook (apollo). The network request is going through and the new products are merged into the cache... but no new products render on the page.
export const FilterableKit = () => {
const selectedKitId = useReactiveVar(selectedKitIdVar);
const [
getKitProducts,
{ data: getKitProductsData, loading: getKitProductsLoading, fetchMore },
] = useGetKitProductsLazyQuery();
useEffect(() => {
if (selectedKitId) {
getKitProducts({
variables: {
getKitsInput: {
_id: {
string: selectedKitId,
filterBy: "OBJECTID" as StringFilterByEnum,
},
},
getProductsInput: {
config: {
pagination: {
reverse: true,
limit: 3,
},
},
},
},
});
}
}, [getKitProducts, selectedKitId]);
const kitProducts = getKitProductsData?.getKits.data?.find(
(kit) => kit?._id === selectedKitId
)?.products.data;
const handleLoadMore = () => {
if (kitProducts && kitProducts?.length > 0) {
const remaining =
getKitProductsData?.getKits.data[0]?.products.stats?.remaining;
if (remaining && remaining > 0) {
const cursor =
kitProducts[kitProducts.length - 1] &&
kitProducts[kitProducts.length - 1]?.createdAt;
fetchMore({
variables: {
getProductsInput: {
config: {
pagination: {
reverse: true,
createdAt: cursor,
},
},
},
},
});
}
}
};
return (
<CContainer>
<KitItemCards products={kitProducts} loading={getKitProductsLoading} />
<CContainer className="d-flex justify-content-center my-3">
<CButton color="primary" className="w-100" onClick={handleLoadMore}>
Load More
</CButton>
</CContainer>
</CContainer>
);
};
Type Policies: I define the "Kit" typePolicy to merge products into the correct field.
export const cache: InMemoryCache = new InMemoryCache({
typePolicies: {
Kit: {
fields: {
products: {
keyArgs: false,
merge(existing = [] as Product[], incoming: GetProductsResponse) {
if (!incoming) return existing;
if (!existing) return incoming;
const { data: products, ...rest } = incoming;
let result: any = rest;
result = [...existing, ...(products ?? [])];
return result;
},
},
},
},
});
Thanks for any pointers in the right direction! Let me know if there is something else you'd like to see.
Hi Guys I'm trying to filter post with data json format field?
"categoryList": ["cat", "cat1"]
For anyone still looking for a solution, this is what I have done for a json type field called tags of a collection type called Articles.
I have two articles in the database with one article having the following values set:
title: "lorem ipsum 1",
tags: [
"test",
"rest"
]
The other article has the following values set:
title: "lorem ipsum 2",
tags: [
"test",
"graphql"
]
My graphql query looks like this:
query {
articlesByTag(limit: 2, where: {tags_include: ["test", "rest"]}, start: 0, sort: "title:asc") {
title,
tags
}
}
While my rest query looks like this:
http://localhost:1337/articlesByTag?limit=2&tags_include[]=test&tags_include[]=rest
This is my articles.js service file:
const { convertRestQueryParams, buildQuery } = require('strapi-utils');
const _ = require('lodash');
const { convertToParams, convertToQuery } = require('../../../node_modules/strapi-plugin-graphql/services/utils');
module.exports = {
async findByTag(ctx) {
let tags_include;
if (ctx.where && ctx.where.tags_include && ctx.where.tags_include.length > 0) {
tags_include = ctx.where.tags_include;
delete ctx.where.tags_include;
} else if (ctx.query && ctx.query.tags_include && ctx.query.tags_include.length > 0) {
tags_include = ctx.query.tags_include;
delete ctx.query.tags_include;
}
if (!Array.isArray(tags_include)) {
tags_include = [tags_include];
}
let filters = null;
if (ctx.query) {
filters = convertRestQueryParams({
...convertToParams(ctx.query)
});
} else {
filters = convertRestQueryParams({
...convertToParams(_.pick(ctx, ['limit', 'start', 'sort'])),
...convertToQuery(ctx.where),
});
}
const entities = await strapi.query('articles').model.query(qb => {
buildQuery({ model: strapi.query('articles').model, filters: filters })(qb);
if (tags_include.length > 0) {
tags_include.forEach((tag) => {
if (tag && tag.length > 0) {
const likeStr = `%"${tag}"%`;
qb.andWhere('tags', 'like', likeStr);
}
});
}
}).fetchAll();
return entities;
},
};
This is the entry needed in routes.js
{
"method": "GET",
"path": "/articlesByTag",
"handler": "articles.findByTag",
"config": {
"policies": []
}
}
This is the controller articles.js
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
async findByTag(ctx) {
const entities = await strapi.services.articles.findByTag(ctx);
return entities.map(entity => sanitizeEntity(entity, { model: strapi.models.articles }));
},
};
And finally this is the schema.graphql.js
module.exports = {
query: `
articlesByTag(sort: String, limit: Int, start: Int, where: JSON): [Articles]
`,
resolver: {
Query: {
articlesByTag: {
description: 'Return articles filtered by tag',
resolverOf: 'application::articles.articles.findByTag',
resolver: async (obj, options, ctx) => {
return await strapi.api.articles.controllers.articles.findByTag(options);
},
},
},
},
};
There is not currently a way to filter the JSON fields yet as of beta.17.8 (latest)
Probably something like that?
strapi.query('cool_model').find({ categoryList: { $all: [ "cat" , "cat1" ] } })
I noticed the library was greatly improved, but I couldn't find how to do a many to many relationship in the library.
For example, if I have users, and restos. I may have "likes", "visited", "wantToGo" all as different many to many relationships. How would I normalize that in normalizr.js?
There's an example of this in the normalizr repo that you should follow. It requires a combination of a mergeStrategy and processStrategy:
import { schema } from '../../src';
const userProcessStrategy = (value, parent, key) => {
switch (key) {
case 'author':
return { ...value, posts: [parent.id] };
case 'commenter':
return { ...value, comments: [parent.id] };
default:
return { ...value };
}
};
const userMergeStrategy = (entityA, entityB) => {
return {
...entityA,
...entityB,
posts: [...(entityA.posts || []), ...(entityB.posts || [])],
comments: [...(entityA.comments || []), ...(entityB.comments || [])]
};
};
const user = new schema.Entity(
'users',
{},
{
mergeStrategy: userMergeStrategy,
processStrategy: userProcessStrategy
}
);
const comment = new schema.Entity(
'comments',
{
commenter: user
},
{
processStrategy: (value, parent, key) => {
return { ...value, post: parent.id };
}
}
);
const post = new schema.Entity('posts', {
author: user,
comments: [comment]
});
export default [post];
My resolver get
{ adminMsg:
[
{active: “y”, text1: “blah1" } ,
{active: “n”, text1: “blah2" }
] };
My query:
{
adminWarn {
adminMsg {
active, text1
}
}
}
I want only array-elements with condition: active = 'y'
I find in GQL Dokumentation no way to write this condition im my query.
Is there any solution in GQL?
Use of resolve args can solve the problem:
const adminWarnList = new GraphQLObjectType({
name: 'adminWarnReportList',
fields: () => ({
adminMsg: {
type: new GraphQLList(adminWarnFields),
},
}),
});
const adminWarn = {
type: adminWarnList,
args: {
active: { type: GraphQLString },
},
resolve: (parent, args, context) => {
...
let reportdata = context.loadData();
if (args.active == 'y') {
let filteredItems = reportdata.filter(function(item) {
return item.active != null && item.active != 'y';
});
reportdata = filteredItems;
}
return { adminMsg: reportdata };
},
};