How do I use a dynamic converter with firebase and TypeScript?

Viewed 1542

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.

0 Answers
Related