简体   繁体   中英

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;
};

Problem Solved: all I had to do is:

const listingsRef = collection(
          db,
          "listings"
        ) as CollectionReference<ListingsDataType>;

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:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM