React - How to map from two SQL tables simultaneously? - javascript

I am pulling from a SQL table device, and displaying its content to a table by mapping from device. I am trying to add a column that pulls information from another SQL table, group, but I haven't figured out how to adjust the mapping in order to pull from both device and group. I am sure the issue is caused since group isn't declared in this scope but I cannot solve how it should be declared in this portion of the script.
Both tables shared a common column, group_id, and I have added useSelector for both:
const device = useSelector((state) => state.device);
const group = useSelector((state) => state.group);
<Table
tableHeaderColor="warning"
tableHead={['Device Name', 'Location', 'Group', 'Release Version']}
tableData={device.deviceData.map((device) => {
return [
device['device_name'],
device['location_name'],
group['group_name'],
device['release'],
];
})}
/>
An alternative fix I have tried is finding the group_name since both tables device and group share the group_id column, but it causes a group.find is not a function error. I am unsure if my syntax is incorrect, as I'm working from this site as a resource.
tableData={device.deviceData.map((device) => {
return [
device['device_name'],
device['location_name'],
group.find(group => group.group_id === device.group_id).group_name,
device['release'],
];
})}
Many thanks for any advice
UPDATE:
Thank you for the answers and comments so far. Here is some additional information:
The reducer does contain the initial state empty array for group (groupdata)
const initialState = {
groupData: [],
result: '',
};
Here are the screenshots of the SQL tables device and group. They do not have the same number of entries, as group lists the groups that a number of devices can be assigned to. Hence there are many more entries under device than group.
device SQL table
group SQL table

If you using group.find you need to make sure the reducer contains the initial state empty array for group

Doing this in a map in the actual JSX might get a bit confusing since you're pulling from two different datasources. Also, assuming that the array in
device.deviceData and the group array share the same number of entries and correlate to each other, searching in group on each loop through device.deviceData seems like an unnecessary performance hit. My preference might be to create my source data in a plain for loop outside of the return JSX, and then just plug it in directly:
let tableData = []
for (let i = 0; i < device.deviceData.length; i += 1) {
const currentDevice = device.deviceData[i];
const currentGroup = group[i];
const entry = [
currentDevice['device_name'],
currentDevice['location_name'],
currentGroup['group_name'],
currentDevice['release'],
];
tableData.push(entry);
}
Then I would simply pass tableData to the <Table /> tableData prop.

I think group is an object and has a structure similar to device.deviceData. By that assumption, group.groupData should be the array which will be useful to us.
What we can do is build a Map of group_id -> group_name which we use later for getting relevant device group_name in Table component.
Here is a code snippet which should give you an idea (this is a JS based answer. I think your use-case can make use of joins at sql level for less work here) :-
const device = {
deviceData: [{
group_id: 1
},
{
group_id: 2
}
]
}
const group = {
groupData: [{
group_id: 1,
group_name: 'Group-1'
},
{
group_id: 2,
group_name: 'Group-2'
}
]
}
const buildMap = () => {
const gMap = {};
for (const {
group_id,
group_name
} of group.groupData) {
gMap[group_id] = group_name;
}
return gMap;
}
const groupMap = buildMap();
device.deviceData.forEach(d => console.log(groupMap[d.group_id]));

Related

React JS: How to pass an array from firebase in query parameter?

I'm using multiselection in one of my parameters and I would like to know how to query those parameters like for example if
I want to query parameters that has 1 (doesn't matter if there are other values)
Value one has : 1, 2, 3
Value two has: 5, 1, 6
Value three has: 5, 6, 9
It should only bring Value one and two
I know you can do something like (for non array values):
const librosRef = db.collection('libros');
const queryRef = librosRef.where('grado', '==', '4° Grado');
and it would bring all the documents in that collection that has 4° Grado but if I try to do that while using a multiselection it doesn't bring anything.
This is what I'm trying (doesn't work for array which is what I'm trying to figure out):
const productosRef = db.collection('productosAIB');
const queryRef = productosRef.where('grado', '==', '4° Grado');
useEffect(() => {
queryRef.orderBy("precio")
.get()
.then((snapshot) => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setProductos(tempData);
});
}, []);
Example of how it gets stored in the Firebase:
And this is how it looks in the table (without the query because if I add the query it doesn't show anything )
It sounds like you're trying to query for documents based on the existence of a value in an array. These are called array membership queries in Firestore.
For this, you would use array-contains to match a single field in an array, and array-contains-any to match any value from an array.
To query based on single value in array
const queryRef = productosRef.where('grado', 'array-contains', '4° Grado');
multiple values passed in as an array
const queryRef = productosRef.where('grado', 'array-contains-any', ['4° Grado', 'next array element']);
NOTE: array-contains-any can support up to 10 comparison values.
For more information on array membership queries you can see the documentation here

Multiple Firebase listeners in useEffect and pushing new event into state

I want to retrieve a list of products in relation to the user's position, for this I use Geofirestore and update my Flatlist
When I have my first 10 closest collections, I loop to have each of the sub-collections.
I manage to update my state well, but every time my collection is modified somewhere else, instead of updating my list, it duplicates me the object that has been modified and adds it (updated) at the end of my list and keep the old object in that list too.
For example:
const listListeningEvents = {
A: {Albert, Ducon}
B: {Mickael}
}
Another user modified 'A' and delete 'Ducon', I will get:
const listListeningEvents = {
A: {Albert, Ducon},
B: {Mickael},
A: {Albert}
}
And not:
const listListeningEvents = {
A: {Albert},
B: {Mickael},
}
That's my useEffect:
useEffect(() => {
let geoSubscriber;
let productsSubscriber;
// 1. getting user's location
getUserLocation()
// 2. then calling geoSubscriber to get the 10 nearest collections
.then((location) => geoSubscriber(location.coords))
.catch((e) => {
throw new Error(e.message);
});
//Here
geoSubscriber = async (coords) => {
let nearbyGeocollections = await geocollection
.limit(10)
.near({
center: new firestore.GeoPoint(coords.latitude, coords.longitude),
radius: 50,
})
.get();
// Empty array for loop
let nearbyUsers = [];
// 3. Getting Subcollections by looping onto the 10 collections queried by Geofirestore
productsSubscriber = await nearbyGeocollections.forEach((geo) => {
if (geo.id !== user.uid) {
firestore()
.collection("PRODUCTS")
.doc(geo.id)
.collection("USER_PRODUCTS")
.orderBy("createdDate", "desc")
.onSnapshot((product) => {
// 4. Pushing each result (and I guess the issue is here!)
nearbyUsers.push({
id: product.docs[0].id.toString(),
products: product.docs,
});
});
}
});
setLoading(false);
// 4. Setting my state which will be used within my Flatlist
setListOfProducts(nearbyUsers);
};
return () => {
if (geoSubscriber && productsSubscriber) {
geoSubscriber.remove();
productsSubscriber.remove();
}
};
}, []);
I've been struggling since ages to make this works properly and I'm going crazy.
So I'm dreaming about 2 things :
Be able to update my state without duplicating modified objects.
(Bonus) Find a way to get the 10 next nearest points when I scroll down onto my Flatlist.
In my opinion the problem is with type of nearbyUsers. It is initialized as Array =[] and when you push other object to it just add new item to at the end (array reference).
In this situation Array is not very convenient as to achieve the goal there is a need to check every existing item in the Array and find if you find one with proper id update it.
I think in this situation most convenient will be Map (Map reference). The Map indexes by the key so it is possible to just get particular value without searching it.
I will try to adjust it to presented code (not all lines, just changes):
Change type of object used to map where key is id and value is products:
let nearbyUsersMap = new Map();
Use set method instead of push to update products with particular key:
nearbyUsersMap.set(product.docs[0].id.toString(), product.docs);
Finally covert Map to Array to achieve the same object to use in further code (taken from here):
let nearbyUsers = Array.from(nearbyUsersMap, ([id, products]) => ({ id, products }));
setListOfProducts(nearbyUsers);
This should work, but I do not have any playground to test it. If you get any errors just try to resolve them. I am not very familiar with the geofirestore so I cannot help you more. For sure there are tones of other ways to achieve the goal, however this should work in the presented code and there are just few changes.

Filter an array based on another array. (Using React)

The goal is to filter an array based on the slots the user has selected.
For example an array has slots for 7pm-9pm,10pm-12pm and so on.
Now the user selects 7pm-9pm, so now I want to filter the array which have 7ppm-9pm or is the users wants
7pm-9pm and 10pm-11pm so the data should be based on 7pm-9pm and 10pm-11pm
Here is how I store the values
This is the original array
data :[
{
name:"something",
phone:"another",
extraDetails : {
// some more data
slots : [
{item:"6PM-7PM"},
{item:"7PM-8pm}
]
}
},{
// Similarly more array with similar data but somewhere slots might be null
}
]
Now for example we have this array
slots:[{6PM-7PM,9PM-10PM,11PM-12AM}]
Now this should filter all those which includes timeslots of 6PM-7PM,9PM-10PM,11PM-12AM
or if the user selects
slots:[{6PM-7PM}]
We should still get the results that includes 6pm-7pm more or else don't matter.
First, I'd suggest using this for your slots representation for simplicity, but you can alter this approach depending on your actual code:
slots: ['6PM-7PM', '9PM-10PM', '11PM-12PM']
Then you can iterate through your data and use filter:
const querySlots = ['6PM-7PM', '9PM-10PM', '11PM-12PM'];
const matchedPersonsWithSlots = data.filter( (person) => {
let i = 0;
while ( i < person.extraDetails.slots.length ) {
if (querySlots.includes(person.extraDetails.slots[i]) return true;
i += 1;
}
return false;
});
matchedPersonsWithSlots will then have all the people that have a slot that matches one of the slots in your query, because if any of the query slots are in a person's list of slots, then it's included in the result set.
EDIT to include a different use case
If, however, every slot in the query array must be matched, then the filtering has to be done differently, but with even less code.
const matchedPersonsWithAllSlots = data.filter(person =>
querySlots.every((qSlot)=>person.extraDetails.slots.includes(qSlot)));
The above will go through each person in your data, and for each of them, determine whether the person has all of your query slots, and include them in the result list, only if this is true.

reduce the time complexity of nested for loop

Basically I am trying to bring down the time complexity of a series of functions that I have
const companies = [
{
staff: [
{
id: 1,
qualities: [
{
id: 2,
tags: [
'efficient',
'hard-working'
]
}
]
}
]
}
]
So I have an array of companies and within that is another array of staff and then again within that there is an array of qualities and finally an array of tags
now I have a predetermined list of tags that I need to match users to for each company like so
companies: [
xyz: [
{
tag: 'smart',
users: []
}
],
// ...
];
so basically I need to loop through each pre-determined tag, then loop through each company, then loop through each user and loop through each tag to create this view
so basically something like this
const tags = [
'smart',
'hard-working',
'efficient'
];
getUserTags(tagName) {
const users = [];
companies.forEach(company => {
company.users.forEach(user => {
user.tags.forEach(tag => {
if (tag === tagName) {
users.push(user);
}
});
});
});
return users;
}
as you can see this is super inefficent and the big O works out to be O(n^4) which is horrible.
How can I solve this?
there could be 50 tags […] so for each tag it would have to [call the getUserTags function and] loop through every user and then loop through each users tags to see if its a match to get the total count.
No, that you shouldn't do. Instead, loop only once through the users and the tags of each user, and collect them in a array-by-tagname map data structure. Use something like
getUsersByTags(companies, tags) {
let map = new Map();
for (const tag of tags) {
map.set(tag, []);
}
for (const company of companies) {
for (const user of company.staff) {
for (const quality of user.qualities) {
for (const tag of quality.tags) {
const n = tag.name;
if (map.has(n))
map.get(n).push(user);
}
}
}
}
return map;
}
You cannot avoid traversing the whole company-staff-quality-tag structure to access all your tag data. Just make sure to not do it more than once.
As far as time complexity goes, you say you want 4 layers of abstraction. (Before the edit to your question it was 3 was users, qualities, tags). Now we have Companies, Staff, Qualities, Tags. With this structure you should be expecting O(n^4). The below code does not try to change the level of complexity, but it might be quicker due to only keeping a count rather than items and using includes to short circuit the exit from a loop.
This can be done using reduce and includes.
let getCountOfTags = (users, tagName) => users.reduce(
(acc, item) => acc + item.qualities.reduce(
(qAcc, q) => qAcc + (q.tags.includes(tagName) ? 1 : 0),
0
),
0
);
We use reduce to get a single value from the accumulation of an array by summing the tag values being identified as being included in each quality item.

Prevent Users From Calling incrementPostCount Function in Firebase Independently of addPost

Let say I have these 2 fields in my firebase database.
user: {
postCount: 2
posts: [
{title: hello, content: world},
{title: hello again, content: world}
]
}
I want the user to have permission to update his posts. but I don't want him to be able to update his post count. I want the post counts to always represent the number of posts and prevent the user from cheating it.
How can I do this in firebase? Is it possible with front end javascript only? If not what would be the option that requires the least server side code possible?
This is the code I'm using but it doesn't prevent users from cheating and just calling the increment function by themselves infinite times.
const push = (objectToInsert, firebasePath) => {
const key = firebase.database().ref().child(firebasePath).push().key
let updates = {}
updates[firebasePath + key] = objectToInsert
firebase.database().ref().update(updates)
}
const increment = (firebasePath) => {
const ref = firebase.database().ref(firebasePath)
ref.transaction( (value) => {
value++
return value
})
}
push(post, `/${user}/${posts}/`)
increment(`/${user}/${postCount}`)
Referring you to firebase rules:
https://firebase.google.com/docs/database/security/#section-authorization
Either you set the rules on each of the user properties based on your security setup and keep the structure as you mentioned, Or move the counts to another node and set the rules (ex. user_post_counts).

Categories