Search Flow in MERN Stack - reactjs

First of all, I tried searching a lot but I am not able to find any resource which satisfies my need. I know there might be some answers already, if you know one please help with the link.
I know how to show search suggestions but I don't know how to show full search results when someone clicks on a search suggestion. Like how to do that in MERN stack with an example if possible.
I need a solution that best fits my scenario:
I have three models,
tags - holds tags
categories - holds categories
items - holds items data - has categories and tags both
currently, I am not storing references to categories and tags table instead
storing a copy directly inside items
Now, I basically want to search the items having the specific categories and tags when someone searches for a keyword.
What I am doing currently is, I search for tags matching the keyword, then categories, then taking out their _id(s) and finding that in items collection
const tags = await Tags.find(
{ tag: { $regex: category.toString(), $options: "i" } },
{ projection: { createdBy: 0 } });
const categories = await Categories.find(
{ category: { $regex: category.toString(), $options: "i" } },
{ projection: { createdBy: 0 } });
const tagsIdArray = tags.map((item) => new ObjectId(item._id));
const catIdArray = categories.map((item) => new
Object(item._id));
$match: {
$and: [
{
$or: [
{ "tags._id": { $in: [...tagsIdArray] } },
{ "category._id": { $in: [...catIdArray] } },
],},],},
And I know that this is not the best way, and it takes a lot of time to search for a given keyword.
Please suggest me schema structure and way to implement search with suggestions.

Related

Delete many items from array of documents in mongodb that match this value

I am trying to delete all documents from an array of documents that match this value but cannot figure it out,
This is my delete query that is not working, courses is the array I need to be in and code is the document I need to check to see its value for deletetion. So if courses.code == 123, then delete and keep going sort of thing.
result = await mongoClient.db(DB_NAME).collection("technologies").deleteMany({ 'courses': {$in: 'courses.code'} });
That is what it looks like in my collection.
"name": "Sass",
"description": "Sass (Syntactically awesome style sheets) is a preprocessor scripting language that is interpreted or compiled into Cascading Style Sheets (CSS).",
"difficulty":2,
"courses":[
{"code":"PROG2700","name":"Client Side Programming"},
{"code":"PROG3017","name":"Full Stack Programming"}
]
},
Any help would be great, thanks!
You can filter documents that contain the field you want to update
Do tests before updates on the original collection.
After some searches, I've found this method:
const filter = { }
db.collection.update(filter,
{
$pull: {
"courses": {
code: {
$in: [
"PROG3017",
"PROG2700"
]
}
}
}
},
{
multi: true
})
This pulls out any element in the array where the value of code is $in the array.

How can I paginate a document array in mongoose using aggregate?

I want to paginate an array of documents called "Reviews" but I can't find a way to do it, since the code I execute paginate the documents for me and not the array of documents "Reviews".
Code you tried to run
return await this.animeModel
.aggregate()
.match({ _id: Types.ObjectId(reviewPaginateData.animeId) })
.unwind('$reviews')
.group({
_id: '$_id',
reviews: { $push: '$reviews' },
})
.sort(reviewPaginateData.sort)
.limit(reviewPaginateData.limit)
.skip(reviewPaginateData.limit * reviewPaginateData.skip)
.exec();
Document structure
Any way to solve this problem? Thank you very much in advance.
I don't have much experience with mongoose, but I can write a shell query to perform the pagination on an array. So, that you can integrate it in mongoose.
The reason you are not able to paginate is that you are applying the skip and limit after group which is giving you an array as output. So, to apply pagination on the array itself you need to use $slice for more read here
db.collection.aggregate([
{
$match:{
"_id":ObjectId("xyz")
}
},
{
$unwind:"$reviews"
},
{
$group:{
"_id":"$_id",
"reviews":{
$push:"$reviews"
}
}
},
{
$sort:{
sort_value:1
}
},
{
$project:{
"reviews":{
$slice:[
"$reviews",
skip_value,
limit_value
]
}
}
}
]).pretty()
Hope this will help :)

How to model recursively nested data in state

I have a data structure typed like:
export interface IGroup {
id: number;
name: string;
groupTypeId: number;
items: IItem[];
groups: IGroup[];
}
Which recursively represents many to many relationships between a "Group" and a "Group" and an "Group" and an "Item". Groups are made up of items and child groups. An item derives to just a simple type and other meta data, but can have no children. A single group represents the top of the hierarchy.
I currently have components, hooks, etc to recursively take a single group and create an edit/create form as shown below:
I have this form "working" with test data to produce a standard data output as below on save:
{
"1-1": {
"name": "ParentGroup",
"groupType": 2
},
"2-4": {
"name": "ChildGroup1",
"groupType": 1
},
"2-9": {
"name": "ChildGroup2",
"groupType": 3
},
"2-1": {
"itemType": "FreeForm",
"selectedName": "Testing",
"selectedClass": 5
},
"2-2": {
"itemType": "FreeForm",
"selectedName": "DisplayTest",
"selectedClass": 5
},
"3-4": {
"itemType": "EnumValue",
"selectedItem": {
"id": 12900503,
"name": "TRUE"
}
},
"3-5": {
"itemType": "EnumValue",
"selectedItem": {
"id": 12900502,
"name": "FALSE"
}
},
"3-9": {
"itemType": "FreeForm",
"selectedName": "Test",
"selectedClass": 5
},
"3-10": {
"itemType": "FreeForm",
"selectedName": "Tester",
"selectedClass": 5
},
"3-11": {
"itemType": "FreeForm",
"selectedName": "TestTest",
"selectedClass": 5
}
}
The "key" to these objects are the grid column and row since there are no other guaranteed unique identifiers (if the user is editing, then it is expected groups have ids in the db, but not if the user is adding new groups in the form. Otherwise, the name is an input form that can be changed.) It makes sense and it is easy to model the keys this way. If another group or item is added to the hierarchy, it can be added with its column and row.
The problem that I have is that I would love to be able to have an add button that would add to a groups items or group arrays so that new rows in the hierarchy could be created. My forms should handle these new entries.
Ex.
"1-1": {
groups: [..., {}],
items: [..., {}]
}
But the only data structure that I have is the IGroup that is deeply nested. This is not good for using as state and to add to this deeply nested state.
The other problem I have is that I need to be able to map the items and groups to their position so that I can translate to the respective db many to many tables and insert new groups/items.
Proposed solution:
I was thinking that instead of taking a group into my recursive components, I could instead create normalized objects to use to store state. I would have one object keyed by column-row which would hold all the groups. Another keyed by column-row to hold all the items. Then I think I would need two more objects to hold many to many relationships like Group to Group and Group to Item.
After I get the data from the form, I hopefully can loop through these state objects, find the hierarchy that way and post the necessary data to the db.
I see that this is a lot of data structures to hold this data and I wasn't sure if this was the best way to accomplish this given my modeling structure. I have just started using Redux Toolkit as well, so I am somewhat familiar with reducers, but not enough to see how I could apply them here to help me. I have been really trying to figure this out, any help or guidance to make this easier would be much appreciated.
Go with normalizing. Each entity having a single source of truth makes it much easier to read and write state.
To do this, try normalized-reducer. It's a simple higher-order-reducer with a low learning curve.
Here is a working CodeSandbox example of it implementing a group/item composite tree very similar to your problem.
Basically, you would define the schema of your tree:
const schema = {
group: {
parentGroupId: { type: 'group', cardinality: 'one', reciprocal: 'childGroupIds' },
childGroupIds: { type: 'group', cardinality: 'many', reciprocal: 'parentGroupId' },
itemIds: { type: 'item', cardinality: 'many', reciprocal: 'groupId' }
},
item: {
groupId: { type: 'group', cardinality: 'one', reciprocal: 'itemIds' }
}
};
Then pass it into the library's top-level function:
import normalizedSlice from 'normalized-reducer';
export const {
emptyState,
actionCreators,
reducer,
selectors,
actionTypes,
} = normalizedSlice(schema);
Then wire up the reducer into your app (works with both React useReducer and the Redux store reducers), and use the selectors and actionCreators to read and write state.

Tell apollo-client what gets returned from X query with Y argiments?

I have a list of Items of whatever type. I can query all of them with query items or one with query item(id).
I realize apollo can't know what will be returned. It knows the type, but it doesn't know the exact data. Maybe there is a way not to make additional request? Map one query onto another?
Pseudo-code:
// somewhere in Menu.tsx (renders first)
let items = useQuery(GET_ITEMS);
return items.map(item => <MenuItemRepresenation item={item} />);
// meanwhile in apollo cache (de-normalized for readability):
{ ROOT_QUERY: {
items: [ // query name per schema
{ id: 1, data: {...}, __typename: "Item" },
{ id: 2, data: {...}, __typename: "Item" },
{ id: 3, data: {...}, __typename: "Item" },
]
}
}
// somewhere in MainView.tsx (renders afterwards)
let neededId = getNeededId(); // 2
let item = useQuery(GET_ITEM, { variables: { id: neededId } } );
return <MainViewRepresentation item={item} />;
Code like this will do two fetches. Even though the data is already in the cache. But it seems apollo thinks on query level. I would like a way to explain to it: "If I make item query, you need to look over here at items query you did before. If it has no item with that id go ahead and make the request."
Something akin to this can be done by querying items in MainView.tsx and combing through the results. It might work for pseudo-code, but in a real app it's not that simple: cache might be empty in some cases. Or not sufficient to satisfy required fields. Which means we have to load all items when we need just one.
Upon further research Apollo Link looks promising. It might be possible to intercept outgoing queries. Will investigate tomorrow.
Never mind apollo link. What I was looking for is called cacheRedirects.
It's an option for ApolloClient or Cache constructor.
cacheRedirects: {
Query: {
node: (_, args, { getCacheKey }) => {
const cacheKey = getCacheKey({
__typename: "Item",
id: args.id,
});
return cacheKey;
},
},
},
I'd link to documentation but it's never stable. I've seen too many dead links from questions such as this.

Query data from firebase Array

My firebase users tree has this structure:
users:
{
{
'userName': 'abc',
'userEmail' : 'abc#abc.com',
'userPreferences':
[
0:'Cinema',
1:'It'
]
},
{
'userName': 'abc',
'userEmail' : 'abc#abc.com',
'userPreferences':
[
0:'Cinema',
1:'Music'
]
}
}
Then, I try to find all users that their preference list contain 'Cinema'.
I try this code:
var ref1 = new Firebase("https://event-application.firebaseio.com/users");
$scope.user = $firebaseArray(ref1.orderByChild("userpreferences").equalTo('Cinema'));
console.log($scope.user);
But I don't get the best result. I get this record:
Your JSON structure shows preferences as userPreferences, so wouldn't the following work?
var ref1 = new Firebase("https://event-application.firebaseio.com/users");
$scope.user = $firebaseArray(ref1.orderByChild("userPreferences").equalTo('Cinema'));
console.log($scope.user);
However I think there is also another problem with your code, you're called an .equalTo('Cinema') however you're comparing it to an array, correct me if i'm wrong but I don't think the behaviour of .equalTo('Cinema') is to loop through each of the values and compare them, I think it's just a straight up comparison
If this is the case, you may need to build a custom query by reading the data from firebase and manipulating it via function available to a snapshot
In NoSQL you'll often end up with a data model that reflects the way your application uses the data. If you want to read all the users that have a preference for Cinema, you should model that in your tree:
users: {
'uid-of-abc': {
'userName': 'abc',
'userEmail' : 'abc#abc.com',
'userPreferences': [
0:'Cinema',
1:'It'
]
},
'uid-of-def': {
'userName': 'def',
'userEmail' : 'abc#abc.com',
'userPreferences': [
0:'Cinema',
1:'Music'
]
}
},
"preferences-lookup": {
"Cinema": {
"uid-of-abc": true,
"uid-of-def": true
},
"It": {
"uid-of-abc": true
},
"Music": {
"uid-of-def": true
}
}
Now you can find out what users prefer cinema with:
ref.child('preferences-lookup/Cinema').on('value', function(snapshot) {
snapshot.forEach(function(userKey) {
console.log(userKey.key()+' prefers Cinema');
});
});
This is covered in this blog post on denormalizing data with Firebase, in the Firebase documentation on structuring data and in dozens of answers here on Stack Overflow. A few:
Storing Relational "Type" or "Category" Data in Firebase Without the Need to Update Multiple Locations
Get Firebase items belonging to category
Retrieve data based on categories in Firebase
How to query firebase for property with specific value inside all children

Resources