1

I'm relatively new to React. I want to make a function that call items data from Firebase using a list of ids.

I use TypeScript, React Native and Hooks.

I try the following code :

const FavoritesScreen = () => {
  const [favoritesData, setFavoritesData] = useState<Array<Item>>([])

  useEffect(() => {
    init()
  }, [])

  const init = async () => {
    const data = await getFavorites(sortByTimeStamp)
    setFavoritesData(data)
  }

  // ...

Someone recommended me to put the declaration of the init function out of the useEffect hook when using async/await. It is working so far.

getFavorites is declared with async as it needs to await data from Firebase.

Visual Studio underline the word data in setFavorites(data) with the following comment:

const data: Promise<Item | undefined>[]
Argument of type 'Promise<Item | undefined>[]' is not assignable to parameter of type 'SetStateAction<Item[]>'.
  Type 'Promise<Item | undefined>[]' is not assignable to type 'Item[]'.
    Type 'Promise<Item | undefined>' is missing the following properties from type 'Item': author, authorName, authorTitle, benefits, and 13 more.ts(2345)

It is not clear for me if the issue is the promise or the undefined, neither how to fix it.

Here is the full code if it can help:

import React, { useEffect, useState, useContext } from 'react'
import { Text, SafeAreaView, StyleSheet } from 'react-native'
import colors from '../constants/colors'
import { FirebaseContext, FavoritesContext } from 'src/firebase'
import { Item } from '../types/item'
import { Timestamp } from '@firebase/firestore-types'

type CompareFunction = (a: [string, Timestamp], b: [string, Timestamp]) => number

const FavoritesScreen = () => {
  const { firebase } = useContext(FirebaseContext)
  const { favorites } = useContext(FavoritesContext)
  const [favoritesData, setFavoritesData] = useState<Array<Item>>([])

  useEffect(() => {
    init()
  }, [])

  const init = async () => {
    // call the functions used in useEffect
    const data = await getFavorites(sortByTimeStamp)
    setFavoritesData(data)
  }

  // This function gets Firestore data for one id
  const getItemDataFromId = async (id: string) => {
    try {
      const data: Item = await firebase.db.collection('content').doc(id).get().data()
      return data
    } catch (error) {
      console.log(`Could not get Firebase data from Id: ${id}. Error message: ${error}`)
    }
  }

  // this function compare the timestamps for use in the Array.prototype.sort() method.
  function sortByTimeStamp(a: [string, Timestamp], b: [string, Timestamp]) {
    let am = 1
    let bm = 1
    try {
      const am = a[1].toMillis()
    } catch {
      return -1
    }
    try {
      const bm = b[1].toMillis()
    } catch {
      return 1
    }
    return am - bm
  }

  // Create an array of items data from the favorites object, sorted ny timestamp
  const getFavorites = async (compareFunction?: CompareFunction) => {
    // Convert the object to a [[key1, value1],[key2,value2],...) array
    const favoritesArray = Object.entries(favorites)
    // sort if needed
    compareFunction && favoritesArray.sort(compareFunction)
    // get the data from firebase
    const items = favoritesArray.map(async ([key, value]) => await getItemDataFromId(key))
    return items
  }

  return (
    <SafeAreaView style={styles.container}>
      <Text>Work in progress</Text>
    </SafeAreaView>
  )
}

//// Styles ////
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: colors.background,
    justifyContent: 'center',
    alignItems: 'center',
  },
})

export default FavoritesScreen
Xiiryo
  • 3,021
  • 5
  • 31
  • 48

1 Answers1

3

The first issue you are likely running into is processing async items within the map function. This is returning an array of promises that resolve to Item rather than an array of Item elements.

An easy fix for this is to wrap this in Promise.all.

const items = favoritesArray.map(async ([key, value]) => await getItemDataFromId(key));
return Promise.all(items);

However, after this, you'll likely run into an issue with the return type being Array<Item | undefined> rather than only Array<Item>. There are a few ways this could be solved. The items value could be casted to Item[], but it's probably preferable to filter out the undefined values. For that, I would recommend looking at this question. For example:

items.filter((x): x is Item => x !== undefined);
skovy
  • 5,430
  • 2
  • 20
  • 34
  • Thanks. Everything is fine now ! – Xiiryo Jul 30 '20 at 18:14
  • `items.filter((x): x is Item => x !== undefined);` I didn't now what the `Item` type was supposed to be so instead I checked if item is truthy to make get rid of undefined issue `items && (await Promise.all(items))` – Newbie21 Aug 13 '21 at 09:08