Pagination Apollo Client 3 - Cache merges but does not render new results when paginating - apollo-client

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.

Related

Array validation not working for PATCH in mongoose

This is my mongoose schema. but when i send PATCH req from client. the options (array) validation not work. but others field's validation work.
I search in online but dont get the problem. How can I slove it. Thank you.
const optionsValidator = function (options) {
console.log("Validating options...");
const MinLength = 2;
const MaxLength = 6;
const MCQCode = 0;
// checking options are required or not for the question;
if (this.questionType !== MCQCode) {
throw new Error("Options are required only for MCQ");
}
if (options.length < MinLength) {
throw new Error(`At least ${MinLength} options are required`);
}
if (options.length > MaxLength) {
throw new Error(`Maximum ${MaxLength} options can be created`);
}
// make options lower case before checking uniqueness of the options
const lowerOptions = options.map((option) => option.toLowerCase());
if (lowerOptions.length !== new Set(lowerOptions).size) {
throw new Error("Options are not unique");
}
// options are validated
return true;
};
const questionSchema = new Schema({
quesId: { type: String, required: [true, "Id_required"] },
title: { type: String, required: [true, "title_required"] },
questionType: {
type: Number,
default: 0,
},
options: {
type: [String],
default: undefined,
required: function () {
return this.questionType === 0 && !this.options;
},
validate: {
validator: optionsValidator,
message: (props) => props.reason.message,
},
},
});
const updatedData = { ...questionData };
let optionsData;
if (updatedData.options) {
data = await Question.findById(id);
optionsData = {
$push: { options: { $each: updatedData.options } },
};
delete updatedData.options;
}
exports.patchQuestion = async (id, questionData) => {
return (result = await Question.findOneAndUpdate(
{ _id: id },
{ ...optionsData, $set: updatedData },
{ new: true, runValidators: true }
));
}
The is the PATCH request send form client.
{ "options": ["A","A"] }

Apollo Graphql fetchMore, updateQuery does not update state

I'm currently trying to implement pagination on my posts.
Using Apollo graphql here is my useQuery
const { data: postsData, fetchMore } = useQuery(POSTS_BY_USER_DRAFT, {
fetchPolicy: 'network-only',
variables: {
user: user.id,
start: 0,
limit: limit
},
onCompleted: () => {
setTotal(postsData[model].meta.pagination.total)
}})
and here is my onClick handler for fetching more posts
const loadMorePosts = async () => {
const nextStart = start + limit
setStart(nextStart);
await fetchMore({
variables: {
user: user.id,
offset: nextStart,
limit: limit,
},
updateQuery: (prevResult, { fetchMoreResult }) => {
if (!fetchMoreResult) {
return prevResult
}
const prevData = prevResult[model].data
const moreData = fetchMoreResult[model].data
fetchMoreResult[model].data = [...prevData, ...moreData]
// fetchMoreResult[model].data = [...moreData]
return fetchMoreResult
},
})}
My queries are successful as I do get correctly the data, however postsData does not get updated
[NOTICED]: If I switch fetchMoreResult[model].data = [...prevData, ...moreData] for
fetchMoreResult[model].data = [...moreData] my postsData does get updated.
I have tried return { ...fetchMoreResult } and multiple ways of returning data fearing an immutability/comparaison issue but it does not seem to do the job.
I'm not sure why, but setting a fetchPolicy for Apollo will do the job
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache({
typePolicies: {
Publication: {
merge: true,
},
Post: {
merge: true,
},
},
}),
defaultOptions: defaultOptions,
})

How to emit once the observable data variable is not NULL

I'm new to RxJS, and I'm trying to figure out how to observe the data when it become available. I'm using Nuxt SSR and I'm fetching data from Firebase. The initial post value is set to null, and once the data object become available, it should run the head() function only once. I get this type error.
Cannot read property 'pipe' of null
If I initiate post: {}, as empty object, I get this type error.
post$.pipe is not a function
Appreciate if I can get some help or guidance.
// page\:post.vue
<script>
import { mapState, mapActions } from 'vuex'
import { take } from 'rxjs/operators'
export default {
fetch() {
this.fetchPost()
},
computed: {
...mapState('posts', ['post']),
},
methods: {
...mapActions('posts', ['fetchPost']),
},
head() {
const post$ = this.post
post$.pipe(take(1)).subscribe((post) => {
return {
title: this.post.title,
link: [{ rel: 'canonical', href: this.post.canonical }],
meta: [
{ hid: 'name', itemprop: 'name', content: this.post.title },
{
hid: 'description',
itemprop: 'description',
content: this.post.content,
},
],
}
})
},
}
</script>
// store\posts.js
export const state = () => ({
post: null,
})
export const mutations = {
setPost(state, payload) {
state.post = payload
},
}
export const actions = {
async fetchPost({ commit }, key) {
const doc = await postsCollection.doc(key).get()
if (doc.exists) commit('setPost', doc.dat())
},
}
Edit
Using Subject. However, there is still issue where the meta tags are generated before the post data is set.
// page\:post.vue
<script>
import { mapState, mapActions } from 'vuex'
import { take } from 'rxjs/operators'
export default {
fetch() {
this.fetchPost()
},
computed: {
...mapState('posts', ['post']),
},
methods: {
...mapActions('posts', ['fetchPost']),
},
head() {
const postSubject = new Subject()
const post = postSubject.asObservable()
postSubject.next(this.post)
post.subscribe((post) => {
return {
title: post.title,
link: [{ rel: 'canonical', href: post.canonical }],
meta: [
{ hid: 'name', itemprop: 'name', content: post.title },
{
hid: 'description',
itemprop: 'description',
content: post.content,
},
],
}
})
},
}
</script>
// store\posts.js
export const state = () => ({
post: null,
})
export const mutations = {
setPost(state, payload) {
state.post = payload
},
}
export const actions = {
async fetchPost({ commit }, key) {
const doc = await postsCollection.doc(key).get()
if (doc.exists) commit('setPost', doc.dat())
},
}
You need to subscribe to an Observable. As I understood, in your case this.post is not type of an Observable.
As this.post is populated at some point of time, you need to subscribe to an observable which should emit data when you say this.post is now populated with data. For that you can use a Subject.
See this example: link

How to update a store in vuex from outside?

I have an iOS app that needs to pass data to a vue front-end:
const customerStore = new Vuex.Store({
state: {
data: [
{ id:1, title: 'Foo' },
{ id:2, title: 'Bar' }
]
},
mutations: {
list (state, data) {
state.data = data
}
}
})
const ListCustomersPage = {
key: 'ListCustomersPage',
template: '#ListCustomersPage',
components: { toolbar, cellcustomer },
data() {
return {
title: 'Select Customer',
items: customerStore.state.data
}
},
methods: {
push() {
}
}
};
However, I need to mutate the store from an injection on the webview:
web.InjectJavascriptAsync("customerStore.commit('list', [])").Start()
But the list is not changed. No error is shown when calling the injection.

How can I select a part of a array of objects in a GraphQL query?

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 };
},
};

Resources