Apollo weirdly alters the query result - javascript

I'm using react-apollos Query-Component in React Native to get data from my backend.
The result looks something like this:
[
{
id: 1,
name: 'US Election',
parties: [
{
name: 'democrats',
id: 4,
pivot: {
id: 3,
answers: [
{
id: 13,
question_id: 3,
reason: 'Lorem ipsum',
__typename: "Answer"
},
{
id: 14,
question_id: 5,
reason: 'Lorem ipsum',
__typename: "Answer"
},
],
__typename: "ElectionPartyPivot"
},
__typename: "Party"
},
],
__typename: "Election"
},
{
id: 2,
name: 'Another one',
parties: [
{
name: 'democrats',
id: 4,
pivot: {
id: 7,
answers: [
{
id: 15,
question_id: 7,
reason: 'Lorem ipsum',
__typename: "Answer"
},
{
id: 18,
question_id: 9,
reason: 'Lorem ipsum',
__typename: "Answer"
},
],
__typename: "ElectionPartyPivot"
},
__typename: "Party"
},
],
__typename: "Election"
}
]
Now, when I console.log the result, the second election "Another one" has the pivot from the first entry US Election.
I think this is because of the normalization that goes on within Apollo (Cause the ID of the parties are the same in both) but I'm unsure how to fix it, so that it does not normalize this or normalizes it correctly.
EDIT
I came up with this solution, but it looks hacky. I now get the election_id together with the party and create a different Identifier within the cache. I wonder if this is good practice?
const cache = new InMemoryCache({
dataIdFromObject: object => {
switch (object.__typename) {
case 'Party': return `${object.election_id}:${object.id}`;
default: return defaultDataIdFromObject(object);
}
}
});
const client = new ApolloClient({
uri: config.apiUrl,
cache
});

Yes, providing a custom dataIdFromObject would be necessary in this case. You should consider using Party:${object.election_id}:${object.id} as the key in case there are other Election fields in the future that will require the same treatment.
This is, at the root, an issue with the way the schema is designed. There's an underlying assumption in GraphQL that while the nodes in your GraphQL may have relationships with one another, they are fully independent of each other as well. That is to say, within the same context, the same node should not represent different data based on the presence or absence of other nodes in the response.
Unfortunately, that's exactly how this response is structured -- we have a node that represents a Party, but its fields are different depending on its relationship to another node -- the Election.
There's two ways to remedy this sort of issue. One way would be to maintain different instances of each Party with different ids for each Election. Rather than representing a political party over the course of its life, the underlying data model behind the Party type would present a political party only in the context of one election.
The other way would be to restructure your schema to more accurately represent the relationships between the nodes. For example, a schema that supported this kind of query:
{
elections {
id
name
parties {
id
name
# omit pivot field on Party type
}
# instead because pivots are related directly to elections, add this field
pivots {
id
answers
# because we may still care what party the pivot is associated with as well
# we can add a party field to pivot to show that relationship
party {
id
name
}
}
}
}

Related

How to create "products filter" efficiently in Node.js and Angular?

I'm creating an angular application (computer online store) with a node/express backened. I have a products page to display all the products in my DB. A product has this model (typescript):
interface Product {
name: string
properties: {name: string, value: string | number}[]
}
I have a section within the page where you can filter products by properties. for instance a user can filter all the CPUs that have 4 cores or 8 cores. right now this is implemented like this:
In the angular application i query ALL THE PRODUCTS of the requested category,
loop through all of them, collect their properties and all the possible values and filter like this...
const products = [
{
name: 'intel cpu 1',
properties: [
{name: 'cores', value: 8},
{name: 'clock speed', value: 2.6}
]
},
{
name: 'intel cpu 2',
properties: [
{name: 'cores', value: 4},
{name: 'clock speed', value: 1.2}
]
}
]
collectPropertiesFromProducts(products)
// RESULT:
[
{property: 'cores', possibleValues: [4,8]},
{property: 'clock speed', possibleValues: [1.2,2.6]}
]
For now it works great, i can filter products easily by the result and it is all dynamic (i can just add a property to a product and thats it).
The problem is that it scales VERY BADLY, because:
I have to query all of the products to know their properties
The more products/properties = more CPU time = blocks main thread
My question is how can i do better? i have a node server so moving all the logic to there its pretty useless, i could also just move the "property collecting" function to a worker thread but again, ill have to query all the products...
Instead of dealing with this in the client or in the service itself, you can let mongodb do the calculations for you. E.g. you could write the following aggregation:
db.getCollection('products').aggregate([{
$unwind: "$properties"
},
{
$project: {
name: "$properties.name",
total: {
$add: ["$properties.value", ]
}
}
}, {
$group: {
_id: "$name",
possibleValues: {
$addToSet: "$total"
}
}
}
])
You could then expose this query through a custom endpoint (e.g. GET /product-properties) on your node-server and consume the response on the client.
You should consider doing multiple requests to the backend:
First:
getQueryParams, a new endpoint which returns your RESULT
Second:
A none filtered request to receive the initial set of products
Third:
When select a filter (based on first request) you do a new request with the selected filter

Javascript Map a Collection

The Issue:
I'm attempting to build a simple search tool. It returns a search query by matching an id to another item with the same id. Without going into the complexities, the issue I'm having is that when my data was organized previously, the map function from javascript returned all the results perfectly. However, now that my data is structured a bit differently (a collection, I think?) ....the ids don't appear to be lining up which causes the wrong search results to show.
The function in question:
const options = this.props.itemIds.map((id) => (
<Option key={this.props.itemSearchList[id].id}>
{this.props.itemSearchList[id].name}
</Option>
));
When the data was structured like this it worked as expected:
Example of previous structure:
const items = [
{
id: 0,
name: "name 0",
tags: ['#sports', '#outdoor', '#clothing'],
},
{
id: 1,
name: "name 1",
tags: ['#sports', '#outdoor', '#clothing'],
},
{
id: 2,
name: "Name 2",
tags: ['#sports', '#outdoor', '#clothing'],
},
Now that the data is a ?collection...the map function doesn't work as anticipated and it returns improper results or none at all: I've been able to use the lodash Map function on this structure successfully in the past.
Here's a screenshot of the new data:
I believe a representative way to write out the example would be:
const newItems = [
0: {
id: 0,
name: "name here",
},
1: {
id: 1,
name: "name here",
},
]
Any recommendations for making this work or need more info? Perhaps I'm misunderstanding the issue entirely, but I believe it has to do with data structure and the map function from JS. I can see results returning, but the id's are not lining up appropriately anymore.
Here's a visual representation of the misalignment. The orange is the search input and it pulling the right result. The green is the misalignment of what it's actually showing because of the data structure and mapping (I assume).
The issue is you were using index and lining that up with id as a sort of pseudo-key which is...beyond fragile. What you should be doing is keying by id (meaing itemsshould be an object) and then having a seperate array that stores the order you want. So items would be an object keyed by id:
const items = {
1: {
id: 1,
name: "name 1",
tags: ['#sports', '#outdoor', '#clothing'],
},
2: {
id: 2,
name: "name 2",
tags: ['#sports', '#outdoor', '#clothing'],
},
9: {
id: 9,
name: "Name 9",
tags: ['#sports', '#outdoor', '#clothing'],
},
};
And then itemIds (which it appears you already have) is an array with the correct order:
const itemIds = [1,9,2];
And then they can be accessed in the right order by looping over that array, and getting the element by said key:
itemIds.map((id) => {
const item = items[id];
// do something with the item
}
Take a look at how Redux recommends normalizing state shape.
https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape
What you call "collections" and "maps" are actually arrays. Now one of the arrays has the objects exactly at the position in the array that matches the id:
items[5].id === 5
Now through sorting /mutating / whatever you change the order so that the element at a certain position doesnt have that as an id:
newItems[5].id // 7 :(
That means that you cannot access the item that easy anymore, you now either have to sort the array again to bring it into order, or you search for an object with the id:
newItems.find(item => item.id === 5) // { id: 5, ... }
Or you switch over to some unsorted collections like a real Map:
const itemsMap = new Map(newItems.map(item => ([item.id, item])));
So you can get a certain item with its id as:
itemsMap.get(5) // { id: 5, ... }
... but the whole thing doesnt have to do with Array.prototype.map at all.
Here was my simple solution:
const options = [];
this.props.itemList.forEach((item) => {
if (this.props.searchResults.includes(item.id)) {
options.push(<Option key={item.id}>{item.name}</Option>);
}
});
Let me know what you think (to the group that tried to help!)

Searches for multiple terms lunrjs

According to official documentation of lunrjs:
"Searches for multiple terms are also supported. If a document matches at least one of the search terms, it will show in the results. The search terms are combined with OR".
Is there any way to achieve that only if a document matches all the search terms it will show the results?
Having these data indexed:
[
{id: 1, text: "foo", body: "jander"}
{id: 2, text: "bar", body: "clander"},
{id: 3, text: "foo bar", body: "jander crispy clander"}
{id: 4, text: "foz", body: "lorem ipsum"}
...
]
If you search by:
idx.search("foo jander clander");
I wish to have only one result ignoring the other two because they don't contain all terms but a few:
// found (1)
{id: 3, text: "foo bar", body: "jander crispy clander"}
but what I have is:
// found (3)
{id: 1, text: "foo", body: "jander"}
{id: 2, text: "bar", body: "clander"},
{id: 3, text: "foo bar", body: "jander crispy clander"}
Thanks in advance for any input.
The pull request you mentioned has been merged and released in version 2.2.0.
Lunr calls this term presence. You can't (yet) directly combine terms with a boolean operator, e.g. foo AND bar, instead you indicate the presence of term in a query. By default a term has optional presence, but it can now also be marked as required or prohibited.
The docs (guide, api) for searching have been updated, but for you specific example you would phrase the query as:
idx.search("+foo +jander +clander")
This is also exposed via the lower level query api:
idx.query(function (q) {
q.term("foo", { presence: lunr.Query.presence.REQUIRED })
q.term("jander", { presence: lunr.Query.presence.REQUIRED })
q.term("clander", { presence: lunr.Query.presence.REQUIRED })
})

Redux: local state id's and/or api uuid's

I'm using Redux with a REST api that uses UUID's. The usual pattern for storing state is using id's as a key for objects:
entities: {
articles: {
1: {
id: 1,
title: 'Some Article',
author: 1
},
2: {
id: 2,
title: 'Other Article',
author: 1
}
},
users: {
1: {
id: 1,
name: 'Dan'
}
}
}
How would I use the UUID's from the api in this? I'd like to be able to create a new entity without having to request the UUID from the server first (for offline capabilities).
Should I:
Use local id's, keep the UUID in a _id property of the entity, and only use it when making an API request? This seems the easiest way, although it feels redundant and I will probably have to search through entities for a certain _id in some cases.
entities: {
articles: {
1: {
_id: 'UUID',
title: 'Some Article',
author: 1
},
2: {
id: 'UUID',
title: 'Other Article',
author: 1
}
},
users: {
1: {
_id: 'UUID',
name: 'Dan'
}
}
}
Use only UUID's from the API, and when creating a new item use a sort if temporary id until the API call is resolved? This seems the best way, although I'm not sure how I would go about changing the id's, which also feels wrong (as they're id's).
entities: {
articles: {
'UUID': {
_id: 'UUID',
title: 'Some Article',
author: 'UUID'
},
'UUID': {
_id: 'UUID',
title: 'Other Article',
author: 'creating'
}
},
users: {
'UUID': {
_id: 'UUID',
name: 'Dan'
},
'creating': {
name: 'Stan'
}
}
}
Do it some other way?
I wouldn't add it to the Redux store until the API returns a response.
In Vue, in my data for a given component I usually have two (sometimes more) root keys, one of which points to the part of my Redux store that handles the data, and the other that is usually form or something of that sort, and the Vue data changes due to binding.
After the user initiates the action to add the resource (POST /resources), and the server returns a successful response, I dispatch an action addedResource. And prior to that I'd dispatch something like addingResource, etc.
Is there any reason this wouldn't work? There shouldn't be a difference using an auto incremented, integer id field vs. a UUID. Your data normalization should still work the same way.

Efficient algorithm / recursive function to nest items from a list

I'm currently implementing my own commenting system. Unfortunately Disqus or any other comment platform doesn't meet my requirements.
I use NodeJS and MongoDB as backend. I need to run basically two queries on my database:
Get all comments by a topic/slug
Get all comments by a user
One can comment to an topic or reply to a comment.
Hey, cool post # top lvl comment
Thanks! # reply to comment
Foo Bar! # reply to reply
and so on...
So my database schema looks like
{
id: ObjectId,
text: string,
author: { id: ObjectId, name: string },
parent: nullable ObjectId,
slug: string/number/whatever
}
If parent is null it's a top level comment, otherwise it's a reply.
Pretty easy so far, right? The problem I do have now is displaying comments below posts. When there would be only top level comments it would be easy. Just get all comments for one specific slug, sort them by date/rating/... and compile them with my HTML View Engine.
But there are in fact replies and I'm just stuck at the point where I need to organize my structure. I want to nest replies into comments within my list
Original list (simplified)
[
{ id: 1, text: 'foo', parent: null },
{ id: 2, text: 'bar', parent: 1 },
// ...
]
Expected Output
[
{ id: 1, text: 'foo', replies: [
{ id: 2, text: 'bar' },
] },
]
I've tried creating my expected output with a recursive function which got very weird tho. Unless that it wouldn't be very efficient. So since I'm really getting frustrated and kinda feeling stupid not solving this problem I've decided to ask for your help SO.
The actual problem I want to solve: How do I render my comments, that they are properly nested etc.
The question I'm going to ask: How do I organize my flat structure in an efficient way to solve the above described problem?
Here's one approach with linear complexity:
var comments = [{
id: 3,
text: 'second',
parent: 1
}, {
id: 1,
text: 'root',
parent: null
}, {
id: 2,
text: 'first',
parent: 1
}, {
id: 5,
text: 'another first',
parent: 4
}, {
id: 4,
text: 'another root',
parent: null
}];
var nodes = {};
//insert artificial root node
nodes[-1] = {
text: 'Fake root',
replies: []
};
//index nodes by their id
comments.forEach(function(item) {
if (item.parent == null) {
item.parent = -1;
}
nodes[item.id] = item;
item.replies = [];
});
//put items into parent replies
comments.forEach(function(item) {
var parent = nodes[item.parent];
parent.replies.push(item);
});
//root node replies are top level comments
console.log(nodes[-1].replies);

Categories

Resources