Skip entity based on a certain attribute - normalizr

I am trying to skip an entity. I tried to do so by returning undefined from idAttribute function however it won't skip. Is this possible?
var { schema, normalize } = require("normalizr")
// Define your article
const article = new schema.Entity('articles', undefined, {
idAttribute: value => value.id
});
const reply = {
articles: [ { id:1, commenter:'foo' }, { id:2, commenter:'bar' }, { dummy:true } ]
}
const normalizedData = normalize(reply, { articles: [ article ] });
console.log('normalizedData.entities:', JSON.stringify(normalizedData.entities));
This gives us the data, normalizedData.entities of:
articles: {
1: {commenter: "foo", id: 1}
2: {commenter: "bar", id: 2}
undefined: {dummy: true}
}
However I want to skip any entries that don't have the key id to not be there. This is a very simplified case of my actual case.

Normalizr does not include complexities for your specific case. You will have to filter those out as another step after the normalization process.

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']})
}

Optimistic response not working when adding items to list

My data model is a list with items. Very simple:
{
_id: 1,
name: "List 1",
items: [
{ _id: 2, text: "Item text 1" },
{ _id: 3, text: "Item text 2" }
]
}
Adding a new list with optimistic response works perfectly:
const [addListMutation] = useAddListMutation({
update: (cache, { data }) => {
const cachedLists =
(cache.readQuery<GetAllListsQuery>({
query: GetAllListsDocument,
})?.lists as TList[]) ?? [];
if (data) {
cache.writeQuery({
query: GetAllListsDocument,
data: {
lists: [...cachedLists, data?.list as TList],
},
});
}
},
});
const addList = async (name: string) => {
const list = {
_id: ..new id here,
name,
items: [],
};
const variables: AddListMutationVariables = {
data: list,
};
await addListMutation({
variables,
optimisticResponse: {
list,
},
});
};
This gets reflected immediately in my component using const { loading, data } = useGetAllListsQuery();. data is updated twice; first with the optimistic response and then after the mutation is done. Just like expected.
Now I'm trying to add an item to the list this way:
const [updateListMutation] = useUpdateListMutation({
update: (cache, { data }) => {
const cachedLists =
(cache.readQuery<GetAllListsQuery>(
{
query: GetAllListsDocument,
},
)?.lists as TList[]) ?? [];
if (data?.list) {
// Find existing list to update
const updatedList = data?.list as TList;
const updatedListIndex = cachedLists.findIndex(
(list: TList) => list._id === updatedList._id,
);
// Create a copy of cached lists and replace entire list
// with new list from { data }.
const updatedLists = [...cachedLists];
updatedLists[updatedListIndex] = { ...updatedList };
cache.writeQuery({
query: GetAllListsDocument,
data: {
lists: updatedLists,
},
});
}
}
});
const updateList = async (updatedList: TList) => {
const variables: UpdateListMutationVariables = {
query: {
_id: updatedList._id,
},
set: updatedList,
};
await updateListMutation({
variables,
optimisticResponse: {
list: updatedList,
},
});
};
const addListItem = async (list: TList, text: string) => {
const updatedList = R.clone(list);
updatedList.items.push({
_id: ...new item id here,
text: 'My new list item',
});
await updateList(updatedList);
};
The problem is is in my component and the const { loading, data } = useGetAllListsQuery(); not returning what I expect. When data first changes with the optimistic response it contains an empty list item:
{
_id: 1,
name: "List 1",
items: [{}]
}
And only after the mutation response returns, it populates the items array with the item with text 'My new list item'. So my component first updates when the mutation is finished and not with the optimistic response because it can't figure out to update the array. Don't know why?
(and I have checked that the updatedLists array in writeQuery correctly contains the new item with text 'My new list item' so I'm trying to write the correct data).
Please let me know if you have any hints or solutions.
I've tried playing around with the cache (right now it's just initialized default like new InMemoryCache({})). I can see the cache is normalized with a bunch of List:1, List:2, ... and ListItem:3, ListItem:4, ...
Tried to disable normalization so I only have List:{id} entries. Didn't help. Also tried to add __typename: 'ListItem' to item added, but that only caused the { data } in the update: ... for the optimistic response to be undefined. I have used hours on this now. It should be a fairly simple and common use case what I'm trying to do :).
package.json
"#apollo/client": "^3.3.4",
"graphql": "^15.4.0",
"#graphql-codegen/typescript": "^1.19.0",

Pattern for multiple types from GraphQL Union

I am learning about Interfaces and Unions in GraphQL (using Apollo Server) and am wondering about something. Using documentation examples, https://www.apollographql.com/docs/apollo-server/schema/unions-interfaces/#union-type, how would I return a result which could return authors and books?
My understanding is that you can only return one object type. If a search result contains and array of both books and authors, how is such a result returned? Can things be structured for this case? I have noticed that __resolveType does not work on an array and can only return a single result (it would return the type for all the objects in the array, not each object in array).
GraphQL TypeDef
const { gql } = require('apollo-server');
const typeDefs = gql`
union Result = Book | Author
type Book {
title: String
}
type Author {
name: String
}
type Query {
search: [Result]
}
`;
Resolver
const resolvers = {
Result: {
__resolveType(obj, context, info){
console.log(obj);
if(obj.name){
return 'Author';
}
if(obj.title){
return 'Book';
}
return null;
},
},
Query: {
search: () => { ... }
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
});
The actual GraphQL query may look something like this and consider the search result is both books and authors:
{
search(contains: "") {
... on Book {
title
}
... on Author {
name
}
}
}
When run, __resolveType(obj, context, info){, obj is:
[{ title: 'A' }, { title: 'B' }, { name: 'C' }]
There's only two ways that would happen:
The search field's type is not actually a list (i.e. it's Result instead of [Result] as shown in the code above.
Your resolver for the search field is returning an array of an array of objects: return [[{ title: 'A' }, { title: 'B' }, { name: 'C' }]]

vue-rx: how to watch value of object from an array is not change anymore?

"vue-rx": "^6.1.0",
"rxjs": "^6.4.0",
"vue": "^2.5.17",
I'm new in vue-rx and rxjs,But when I see several demo of rx, I'm quite interested in this.So I want to use it in my project which posts a request when attribute num will not change anymore
[
{
id: 0,
name: 'giftA',
num: 0 // will turn to 1,2,3,4,5,...after running `send({id: 0})` function 1,2,3,4,5,...times
},
{
id: 1,
name: 'giftB',
num: 0
},
...
]
And Here is my solution:
using $watchAsObservable to watch the change of sendCalledTimes, and then using mergeMap to post the request.
the variable sendCalledTimes is a number which will sendCalledTimes++ when called send function, And after posting the request, reset this to sendCalledTimes = 0.
So that $watchAsObservable('sendCalledTimes')(vue-rx) will execute every three seconds, and will reduce request times in my project. But i think it's still not good because it just like a timer and can't watch weather num of each object in the Array changes. The good example should be like this search example.
data() {
return {
sendCalledTimes: 0,
giftArr: []
}
},
created() {
this.$watchAsObservable('sendCalledTimes').pipe(
pluck('newValue'),
filter(val => val > 0),
debounceTime(3000),
// if `sendCalledTimes` is the same number as previous
// will not execute follows
// distinctUntilChanged(),
mergeMap(
(val) => this.requestSendGift()
),
).subscribe(
(val) => { }
)
},
methods: {
send (obj) {
let pushFlag = true
for (const gift in this.giftArr) {
if (gift.id === obj.id) {
gift.num++
pushFlag = false
break
}
}
if (pushFlag) {
this.giftArr.push(obj)
}
// observable
this.sendCalledTimes++
},
async requestSendGift () {
for (const gift in this.giftArr) {
// example for post a request to store each gift
await axios({
data: gift,
type: 'post',
url: '...'
}).then(res => { ... })
}
// reset `this.sendCalledTimes`
this.sendCalledTimes = 0
}
}
Also since vue-rx doesn't have many examples on github, so i need help to solve creating good subscription for this situation.
I have tried this, but failed:
data () {
return {
giftArr: []
}
},
subscriptions: {
test: from(this.giftArr) // console.log(this.$observables.test) throw an error: typeError: Cannot read property 'giftArr' of undefined
},
It would be greatly appreciated if anyone can help me to solve this question.
It's a little unclear from your question exactly what you're trying to do, but I've created an example based on what I believe to be your intent.
I made some assumptions:
You have a 'gifts' array that represents all of the gifts that will ever exist.
You want to make updates to that array.
Every time you make an update to the array, you want to see the update in the form of an Observable emitting an event.
Use a Subject
I think what you want is a Subject.
const gift$ = new Subject();
Make it Emit on Updates
And you would set it up to emit every time you increment num or add a new gift.
function addGift(gift) {
gifts.push(gift);
gift$.next(gift);
}
function incrementGift(gift) {
gift.num++;
gift$.next(gift);
}
All together it could look something like this:
import { Subject } from 'rxjs';
const gift$ = new Subject();
const gifts = [{ id: 0, name: 'giftA', num: 0 }, { id: 1, name: 'giftB', num: 0 }];
function addGift(gift) {
gifts.push(gift);
gift$.next(gift);
}
function incrementGift(gift) {
gift.num++;
gift$.next(gift);
}
function sendGift(newGift) {
const currentGift = gifts.find(g => g.id === newGift.id);
currentGift ? incrementGift(currentGift) : addGift(newGift);
}
gift$.subscribe(update => {
console.log(gifts);
console.log(update);
});
// You should see an initial logging of 'gifts' and update will be 'undefined' at first. Then you'll see a log for every 'sendGift'.
sendGift({ id: 0 });
sendGift({ id: 3, name: 'giftC', num: 0 });
StackBlitz

How to udpate an entry in graphql using variables

I'm using GraphQL plugin with strapi cms if it matters.
I cannot figure out how to update an existing query using a dynamic variable. My original mutation without using variables:
mutation {
updateExam(input: {
where: {
id: "1234"
},
data: {
questions: "hello"
}
}) {
exam {
questions
}
}
}
I learned that if I would like to create a new entry using variables I should write it like so (answer by David Maze here: How to pass JSON object in grpahql and strapi):
const response = await strap.request('POST', '/graphql', {
data: {
query: `mutation CreateExam($input: CreateExamInput!) {
createExam(input: $input) {
exam { name, desription, time, questions }
}
}`,
variables: {
input: {
name: examInfo.newExamName,
desription: examInfo.newExamDescription,
time: Number(examInfo.newExamTime),
questions: [{ gf: "hello" }],
subjects: [this.state.modalSubjeexisting
}
}
}
});
But how can I update an exising query? Where should I put the
where: {id: "1234"}
How can I provide the existing id of the entry?
I don't know about this strapi cms, but by the way it looks the mutation you have already working, I'd try something like this for the update one:
const response = await strap.request('POST', '/graphql', {
data: {
query: `mutation UpdateExam($input: UpdateExamInput!) {
updateExam(input: $input) {
exam {
questions
}
}
}`,
variables: {
input: {
where: {
id: examInfo.id
},
data: {
questions: [{ gf: "hello" }]
}
}
}
}
});
Give it a try and see if it works.

Resources