Why is my Many to Many relationship field undefined? - arrays

I am trying to create a follower/following system and when I try to append the new user to the following list I get the error Cannot read property 'push' of undefined. This ends up creating 2 separate tables one for users following other users and one for users being followed by other users. Not sure why it's not picking up the field? Any help is appreciated.
import { Length } from "class-validator";
import {
Column,
CreateDateColumn,
Entity,
JoinTable,
ManyToMany,
OneToMany,
PrimaryColumn,
RelationCount,
Unique,
UpdateDateColumn
} from "typeorm";
export class User {
#PrimaryColumn()
public user_id: string;
#Column()
public first_name: string;
#Column()
public last_name: string;
#Column()
public email: string;
#Column()
public phone_number: string;
#Column()
public username: string;
#Column()
#CreateDateColumn()
public created_on: Date;
#Column()
#UpdateDateColumn()
public updated_at: Date;
#ManyToMany((type) => User, (user) => user.following)
#JoinTable()
public followers: User[];
#ManyToMany((type) => User, (user) => user.followers)
#JoinTable()
public following: User[];
#RelationCount((user: User) => user.followers)
public followers_count: number;
#RelationCount((user: User) => user.following)
public following_count: number;
}
const { user_id,
follow_user_id } = req.
const user_repo = getRepository(User);
const user = await user_repo.findOne({
where: {user_id}
});
const follow_user = new User();
follow_user.user_id = follow_user_id;
user.following.push(follow_user);
const result = user_repo.save(user);
Error is referring to this line user.following.push(follow_user);
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'push' of undefined

I encountered a similar error with OneToMany and ManyToOne relations where the relative returned null/undefined.
The workaround I'm using involves putting this in the User class:
#AfterLoad()
async nullChecks() {
if (!this.followers) {
this.followers = []
}
if (!this.following) {
this.following = []
}
}
documentation

I didn't test ways below, but think one of them should help you.
1st way. At your User class.
// Source code omission
#ManyToMany((type) => User, (user) => user.followers)
#JoinTable()
public following: User[] = []; // ★ Added assign
// Source code omission
2nd way. At your User class.
export class User {
// Source code omission
constructor() { // ★ Added line
this.following = []; // ★ Added line
} // ★ Added line
}
3rd way. At place where you use User class.
const follow_user = new User();
follow_user.user_id = follow_user_id;
user.following = []; // ★ Added line
user.following.push(follow_user);
const result = user_repo.save(user);
4th way. At place where you use User class.
const follow_user = new User();
follow_user.user_id = follow_user_id;
user.following = [follow_user]; // ★ Edited line
const result = user_repo.save(user);

We use this approach to avoid undefined lists:
#ManyToMany((type) => User, (user) => user.following)
#JoinTable()
private _followers: User[];
...
get followers() : User[] {
if (!_followers) {
_followers = [];
}
return _followers;
}

Related

Why is my filter for an array returning an empty list when there should be multiple matching items

My data is stored in two separate tables; "posts" and "profiles". Each User object comes from the "profiles" table but also has a list posts which is not a column in "profiles". Because of this, I need to fetch the posts first, then their corresponding users, then add each post to their User object based on "uid". My function below works for most of that but every user has an empty posts list, even when there should be posts.
const [posts, setPosts] = useState<Array<Post>>([]);
const [profiles, setProfiles] = useState<Array<User>>([]);
useEffect(() => {
async function fetchData() {
const { data: postsData } = await supabase.from("posts").select("*");
const postUids = postsData!.map((post) => post.uid);
const { data: profilesData } = await supabase
.from("profiles")
.select("*")
.in("uid", postUids);
setPosts(postsData!.map((post) => new Post(post)));
const profiles = profilesData!.map((userData: any) => {
const userPosts: Array<Post> = posts.filter(
(post) => post.uid === userData.uid
);
console.log("User posts: " + userPosts);
const user = new User({ ...userData, posts: userPosts });
// user.posts = [...user.posts, ...userPosts];
console.log(user);
return user;
});
setProfiles((prevUsers) => [...prevUsers, ...profiles]);
console.log(profiles);
}
fetchData();
}, []);
console.log(posts);
console.log(profiles);
Example of postsData:
[{
"caption":"Caption",
"date":"1669244422569",
"imageUrls":[
"https://cdn.pixabay.com/photo/2020/05/04/16/05/mckenzie-river-5129717__480.jpg"
],
"location":{
"latitude":150,
"locationInfo":"City, State",
"longitude":-150
},
"postId":"1669244407166",
"uid":"daf6b8be-7cd0-4341-89d7-07879b207087"
}]
Post object:
export default class Post {
imageUrls: Array<string>;
postId: string;
uid: string;
caption: string;
location: Location;
date: number;
constructor(post: any) {
this.imageUrls = post.imageUrls;
this.postId = post.postId;
this.uid = post.uid;
this.caption = post.caption;
this.location = post.location;
this.date = post.date;
}
}
Example of profilesData:
{
"blockedUsers":[],
"displayName":"name",
"photoURL":"https://cdn.pixabay.com/photo/2020/05/04/16/05/mckenzie-river-5129717__480.jpg",
"uid":"daf6b8be-7cd0-4341-89d7-07879b207087",
"verified":false
}
User object:
export default class User {
uid: string;
blockedUsers: Array<string>;
posts: Array<Post>;
photoURL: string;
displayName: string;
verified: boolean;
constructor(user: any) {
this.uid = user.uid;
this.blockedUsers = user.blockedUsers;
this.posts = user.posts;
this.photoURL = user.photoURL;
this.displayName = user.displayName;
this.verified = user.verified;
}
}
Not entirely sure why you are not getting any posts data, might be due to how your RLS is configured, but there is a better way to query your data.
You can query posts and profiles at the same time like this:
const { data, error } = await supabase.from("profiles").select("*, posts(*)");
This way, you don't have to do another query to retrieve the profiles separately, and you also don't have to loop through the retrieved objects to modify them.

Argument of type Promise<MemberEntityVM[]> is not assignable to parameter of type SetStateAction<MemberEntityVM[]>

I want to show a list of members of Github, filtered by organization (for instance, members of Github which are employees of Microsoft). I am using React + TS. So, I have the following API Model (the interface of the JSON data that I receive after doing the call to the GitHub API):
export interface MemberEntityApi {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: string;
}
But I dont wanna use so much information, so I will create a ViewModel (just with the information I want to use). Here my ViewModel:
export interface MemberEntityVM {
id: number;
login: string;
avatar_url: string;
}
Here my call to the API:
export const getMembers = (organization: string, perpage, page): Promise<MemberEntityApi[]> => {
return fetch(`https://api.github.com/orgs/${organization}/members?per_page=${perpage}&page=${page}`)
.then((response) => {
if (response.status == 200) {
const data = response.json();
return data;
} else {
throw new Error(response.statusText)
}
})
.catch((e) => console.log(e));
}
Here my mapper function:
export const mapMemberEntityFromAPIModelToVM = (data: MemberEntityApi[]): MemberEntityVM[] => {
return data.map((data) => {
return {
id: data.id,
login: data.login,
avatar_url: data.avatar_url
}
})
}
So, in my component, the problem is the following one:
const [members, setMembers] = React.useState<MemberEntityVM[]>([]);
const [organization, setOrganization] = React.useState<string>("Microsoft");
useEffect(() => {
const membersVM = getMembers(organization, perpage, page)
.then((data) => mapMemberEntityFromAPIModelToVM(data));
console.log("membersVM", membersVM);
setMembers(membersVM);
console.log("members", members);
}, [])
This sentence console.log("membersVM", membersVM); is showing a response with an array of 3 members of a given organization (that is correct, since I just want to show 3 members per page) but this sentence console.log("members", members); shows an empty array. That means setMembers(membersVM); is not working. But I dont understand why. I am receiving the following mistake: "Argument of type Promise<MemberEntityVM[]> is not assignable to parameter of type SetStateAction<MemberEntityVM[]>" in the line I do setMembers(membersVM);. I dont understand this error message since membersVM has the following interface MemberEntityVM[] and the type of useState is the following one: React.useState<MemberEntityVM[]>. Where am I committing the mistake? Thanks in advance, kind regards.
Because getMembers(..).then(..) returns a promise of the mapped members, you will need to either await the value or deal with it in the then handler. Since useEffect does not allow an asynchronous function as its argument, the easiest solution is to call setMembers in the existing then handler:
useEffect(() => {
getMembers(organization, perpage, page)
.then((data) => {
const membersVM = mapMemberEntityFromAPIModelToVM(data);
console.log("membersVM", membersVM);
setMembers(membersVM);
console.log("members", members);
});
}, [])
If you do wish to use async/await, you can declare a local async function fetchMembers, and immediately call it:
useEffect(() => {
const fetchMembers = async () => {
const data = await getMembers(organization, perpage, page);
const membersVM = mapMemberEntityFromAPIModelToVM(data);
console.log('membersVM', membersVM);
setMembers(membersVM);
console.log('members', members);
}
fetchMembers();
}, [])

Why can't I save the 'id' on MongoDB using Deno backend?

I am trying to save the id that I get from the MongoDB while saving the stock information as a property of stock using the following code but I get some errors:
async save(stock : Stock) {
const id = await stockCollection.insertOne(stock);
console.log('this is the id: ', id.toString());
stock.id = id.toString();
console.log(stock.id);
// delete stock._id;
return this;
}
The result of
console.log('this is the id: ', id.toString());
is:
this is the id: 621e826f90e8bf45a3fe493d
And the result of
console.log(stock.id);
Is also the same:
621e826f90e8bf45a3fe493d
But when I check the database I see the saved document like below:
{"_id":{"$oid":"621e7fc2f5b14f28463f289f"},"id":"","open":"2660.7250","high":"2660.7250","low":"2660.5700","close":"2660.5700","volume":"1826"}
It seems the line stock.id = id.toString(); doesn't work and it can not put the id into the id property of the stock.
Also when I try to remove the _id property of the saved stock using this line of the code:
delete stock._id;
It gives me this error:
Property '_id' does not exist on type 'Stock'.deno-ts(2339)
What is the problem and how can I resolve it?
EDIT: stock is an instance of Stock class that loads data of a stock using alpha vantage library.
stock.ts:
import { stockCollection } from "../mongo.ts";
import BaseModel from "./base_model.ts";
export default class Stock extends BaseModel {
public id: string = "";
public open: string = "";
public high: string = "";
public low: string = "";
public close: string = "";
public volume: string = "";
constructor({ id = "", open = "", high = "", low = "" ,close = "", volume = "" }) {
super();
this.id = id;
this.open = open;
this.high = high;
this.low = low;
this.close = close;
this.volume = volume;
}
static async findOne(params: object): Promise<Stock | null> {
const stock = await stockCollection.findOne(params);
if (!stock) {
console.log('there is no stock');
return null;
}
console.log('there is a stock');
return new Stock(Stock.prepare(stock));
}
async save(stock : Stock) {
const id = await stockCollection.insertOne(stock);
console.log('this is the id: ', id.toString());
stock.id = id.toString();
console.log(stock.id);
// delete stock._id;
return this;
}
}
BaseModel.ts:
export default class BaseModel {
public static prepare(data: any) {
data.id = data._id.toString();
// delete data._id;
return data;
}
}
Here I am trying to save one instance of a stock in database:
export const stockSave = async () => {
const YOUR_API_KEY = '***********';
const alpha = new AlaphaVantage('***********');
const writestock = await alpha.stocks.intraday('GOOG' , '1min' ).then((data: any) => {return data["Time Series (1min)"]["2022-02-28 14:31:00"]} );
console.log('this is writestock' , writestock);
const stock = new Stock({
id: "",
open: writestock["1. open"],
high: writestock["2. high"],
low: writestock["3. low"],
close: writestock["4. close"],
volume: writestock["5. volume"]});
await stock.save(stock);
}
Edit2: This is the whole project code: https://github.com/learner00000/back
Because you don't instantiate each instance of Stock with an actual ID, and you are instead relying on the ID to be generated by MongoDB, you should not store the id property in the database. You'll probably need to update your schema to reflect this.
You haven't shown a reproducible example with all of the imports and data, so I'm left to guess a bit about some types, but you can refactor the relevant parts of the class like this:
TS Playground
const stockDataKeys = ['close', 'high', 'low', 'open', 'volume'] as const;
type StockDataKey = typeof stockDataKeys[number];
type StockData = Record<StockDataKey, string>;
class Stock extends BaseModel implements StockData {
public id: string;
public close: string;
public high: string;
public low: string;
public open: string;
public volume: string;
constructor (init: Partial<StockData & { id: string; }> = {}) {
super();
this.id = init.id ?? '';
this.close = init.close ?? '';
this.high = init.high ?? '';
this.low = init.low ?? '';
this.open = init.open ?? '';
this.volume = init.volume ?? '';
}
getData (): StockData {
const data = {} as StockData;
for (const key of stockDataKeys) data[key] = this[key];
return data;
}
hasId (): boolean {
return Boolean(this.id);
}
async save (): Promise<void> {
const data = this.getData();
const id = await stockCollection.insertOne(data);
this.id = id.toString();
}
}

Firebase 9 with React Typescript: How do I change the querySnapshot types?

When I'm trying to save a snapshot of my query from firestore it returns as q: Query<DocumentData>, and my query snapshot is querySnap: QuerySnapshot<DocumentData>.
DocumentData type is: [field: string]: any; And I can't loop through it without getting any errors.
my effect Code
useEffect(() => {
const fetchListings = async () => {
try {
// Get reference
const listingsRef = collection(db, "listings");
// Create a query
const q = query(
listingsRef,
where("type", "==", params.categoryName),
orderBy("timestamp", "desc"),
limit(10)
);
// Execute query
const querySnap = await getDocs(q);
let listings: DocumentData | [] = [];
querySnap.forEach((doc) => {
return listings.push({ id: doc.id, data: doc.data() });
});
setState((prevState) => ({ ...prevState, listings, loading: false }));
} catch (error) {
toast.error("Something Went Wront");
}
};
if (mount.current) {
fetchListings();
}
return () => {
mount.current = false;
};
}, [params.categoryName]);
Does anyone know how to set my listings type correctly?
The listings type from firestore should be:
type GeoLocationType = {
_lat: number;
_long: number;
latitude: number;
longitude: number;
};
export type ListingsDataType = {
bathrooms: number;
bedrooms: number;
discountedPrice: number;
furnished: boolean;
geolocation: GeoLocationType;
imageUrls: string[];
location: string;
name: string;
offer: boolean;
parking: boolean;
regularPrice: number;
timestamp: { seconds: number; nanoseconds: number };
type: string;
userRef: string;
};
As you've discovered, you can simply coerce the type of the Reference:
const listingsRef = collection(db, "listings") as CollectionReference<ListingsDataType>;
However, while this works, you may run into future issues where you encounter an unexpected type or you have other nested data that doesn't translate to Firestore very well.
This is where the intended usage of the generic type comes in where you instead apply a FirestoreDataConverter object to the reference to convert between DocumentData and the type of your choosing. This is normally used with class-based types.
import { collection, GeoPoint, Timestamp } from "firebase/firestore";
interface ListingsModel {
bathrooms: number;
bedrooms: number;
discountedPrice: number;
furnished: boolean;
geolocation: GeoPoint; // <-- note use of true GeoPoint class
imageUrls: string[];
location: string;
name: string;
offer: boolean;
parking: boolean;
regularPrice: number;
timestamp: Date; // we can convert the Timestamp to a Date
type: string;
userRef: string; // using a converter, you could expand this into an actual DocumentReference if you want
}
const listingsDataConverter: FirestoreDataConverter<ListingsModel> = {
// change our model to how it is stored in Firestore
toFirestore(model) {
// in this case, we don't need to change anything and can
// let Firestore handle it.
const data = { ...model } as DocumentData; // take a shallow mutable copy
// But for the sake of an example, this is where you would build any
// values to query against that can't be handled by a Firestore index.
// Upon being written to the database, you could automatically
// calculate a `discountPercent` field to query against. (like "what
// products have a 10% discount or more?")
if (data.offer) {
data.discountPercent = Math.round(100 - (model.discountedPrice * 100 / model.regularPrice))) / 100; // % accurate to 2 decimal places
} else {
data.discountPercent = 0; // no discount
}
return data;
},
// change Firestore data to our model - this method will be skipped
// for non-existant data, so checking if it exists first is not needed
fromFirestore(snapshot, options) {
const data = snapshot.data(options)!; // DocumentData
// because ListingsModel is not a class, we can mutate data to match it
// and then tell typescript that it is now to be treated as ListingsModel.
// You could also specify default values for missing fields here.
data.timestamp = (data.timestamp as Timestamp).toDate(); // note: JS dates are only precise to milliseconds
// remove the discountPercent field stored in Firestore that isn't part
// of the local model
delete data.discountPercent;
return data as ListingsModel;
}
}
const listingsRef = collection(db, "listings")
.withConverter(listingsDataConverter); // after calling this, the type will now be CollectionReference<ListingsModel>
This is documented in the following places:
Custom Objects with Firestore
FirestoreDataConverter
CollectionReference#withConverter(converter)
Problem Solved: all I had to do is:
const listingsRef = collection(
db,
"listings"
) as CollectionReference<ListingsDataType>;

Angular - Why am I getting object of objects instead of array of objects?

Component:
applicants: UserProfile[];
ngOnInit() {
this.store
.pipe(select(fromRootUserProfileState.getUserProfiles))
.subscribe((userProfiles: UserProfile[]) => {
this.applicants = userProfiles;
console.log('this.applicants:', this.applicants);
});
}
UserProfile interface:
export interface UserProfile {
id: number;
fullName: string;
roles?: Role[];
windowsAccount?: string;
firstName?: string;
lastName?: string;
email?: string;
managerName?: string;
managerId?: number;
managerEmail?: string;
companyId?: number;
companyName?: string;
countryId?: number;
country: Country;
countryName?: string;
}
NgRx effect:
#Injectable()
export class UserProfileEffects {
constructor(
private actions$: Actions,
private userProfileService: UserProfileService
) {}
#Effect()
loadUserProfiles$: Observable<Action> = this.actions$.pipe(
ofType(userProfileActions.UserProfileActionTypes.LoadUPs),
mergeMap((action: userProfileActions.LoadUPs) =>
this.userProfileService.getUserProfiles().pipe(
map(
(userProfiles: Array<UserProfile>) =>
new userProfileActions.LoadSuccessUPs(userProfiles)
),
catchError((err) => of(new userProfileActions.LoadFailUPs(err)))
)
)
);
}
The service:
getUserProfiles(): Observable<UserProfile[]> {
return this.http.get<UserProfile[]>(this.baseUrl + 'userprofiles/getuserprofiles');
}
Backend API (.Net Core):
[HttpGet("[action]")]
public async Task<IActionResult> GetUserProfiles()
{
var userProfiles = await _userProfileRepository.GetUserProfiles();
var userProfileViewModels = _mapper.Map<IEnumerable<UserProfileViewModel>>(userProfiles);
return Ok(userProfileViewModels);
}
But in the component, I cannot treat my "applicants" array as a proper array, it is some sort of an object. This is happening all over my code, why am I not getting array of objects as I explicitly have defined?
I can suggest workaround for now. You can convert response to to array from below code.
for (var key in userProfiles ) {
if (userProfiles.hasOwnProperty(key)) {
var val = userProfiles[key];
this.applicants.push(val)
}
}
After this you can do push operation applicants variable.

Resources