I am trying to dynamically pass a converter to firebase's withConverter. If you look at the docs on FirestoreDataConverter, the example provided works if the converter is passed directly into withConverter.
// I have modified Post Class as seen in the docs, with additional types
class Post {
constructor(readonly title: string, readonly author: string) {
this.title = title;
this.author = author;
}
toString(): string {
return `${this.title}, by ${this.author}`;
}
}
type PostConverterType = firebase.firestore.FirestoreDataConverter<Post>;
// Example converter from the docs
const postConverter: PostConverterType = {
toFirestore(post: Post): firebase.firestore.DocumentData {
return { title: post.title, author: post.author };
},
fromFirestore(
snapshot: firebase.firestore.QueryDocumentSnapshot,
options: firebase.firestore.SnapshotOptions
): Post {
const data = snapshot.data(options);
return new Post(data.title, data.author);
},
};
// Example when the converter is passed directly into with conveter
// This works without throwing warning.
const postSnap = await firebase
.firestore()
.collection('posts')
.withConverter(postConverter) // Works like a charm, but not ideal for reuse
.doc()
.get();
const post = postSnap.data();
if (post !== undefined) {
console.log({
title: post.title, // string
toString: post.toString(), // string
});
}
Above approach works, but not so reusable in my case. My headache is at passing a converter as a variable in a reusable function like this .withConverter(converterVaribleFromProps).
For example if I add another data to convert like Authors
// Sample Author Format
class Author {
constructor(readonly name: string, readonly email: string) {
this.name = name;
this.email = email;
}
}
type AuthorConverterType = firebase.firestore.FirestoreDataConverter<Author>;
const authorConverter: AuthorConverterType = {
toFirestore(author: Author): firebase.firestore.DocumentData {
return { title: author.name, author: author.email };
},
fromFirestore(
snapshot: firebase.firestore.QueryDocumentSnapshot,
options: firebase.firestore.SnapshotOptions
): Author {
const data = snapshot.data(options);
return new Author(data.title, data.author);
},
};
At this point I have an Authors view that only cares about the authorConverter and a Posts view for the postConverter. I created a reusable function that takes in a collection and converter and passes them to firebase. Doing alot of repeatitive logic for both views.
export const connectToDbFromAnyContext = (
collection: 'posts' | 'users' = 'posts',
converter: PostConverterType | AuthorConverterType // 1. I am stuck here
): (() => void) => {
// Tried to test typeof converter here
// and do conditional checks by in vain
console.log({ 'converterType': typeof converter });
const unsubscribeDb = firebase
.firestore()
.collection(collection)
.withConverter(converter) // 2. Because this complains
.onSnapshot(
(snapshot) => {
const newData = snapshot.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
// Update state with newTimes
console.log({ newData });
},
(error) => {
console.log({ error });
}
);
// Do some other stuff here
// ...and alot or other repeatitive logic
return unsubscribeDb;
};
As you can see at Comment 1, I am trying my luck with the Union types with converter: PostConverterType | AuthorConverterType but TSlint throws this error... Comment 2
Argument of type 'PostConverterType | AuthorConverterType' is not assignable to parameter of type 'FirestoreDataConverter<Post>'.
Type 'AuthorConverterType' is not assignable to type 'FirestoreDataConverter<Post>'.
Types of property 'toFirestore' are incompatible.
Type '{ (modelObject: Author): DocumentData; (modelObject: Partial<Author>, options: SetOptions): DocumentData; }' is not assignable to type '{ (modelObject: Post): DocumentData; (modelObject: Partial<Post>, options: SetOptions): DocumentData; }'.
Types of parameters 'modelObject' and 'modelObject' are incompatible.
Type 'Post' is missing the following properties from type 'Author': name, emailts(2345)
Please help. Thank you.