Home > front end >  How to type promises on react context with typescript
How to type promises on react context with typescript

Time:02-27

I'm learning to implement typescript in react and I'm struggling trying to implement a couple of async functions on context.

The error I'm getting is the following:

Argument of type '{ userData: null; favoriteCocktails: never[]; publishedRecipes: never[]; fetchUserData: (user: any) => PromiseConstructor; fetchFavoriteCocktails: (userData: UserData | null) => PromiseConstructor; fetchPublishedRecipes: (user: any) => PromiseConstructor; }' is not assignable to parameter of type 'ProfileContext'.
  The types returned by 'fetchUserData(...)' are incompatible between these types.
    Type 'PromiseConstructor' is missing the following properties from type 'Promise<void>': then, catch, finally, [Symbol.toStringTag]ts(2345)

This is my context:

import { createContext } from 'react'
import {UserData , Cocktail} from '../types'

interface ProfileContext {
    userData: UserData | null,
    favoriteCocktails: Array<Cocktail>,
    publishedRecipes: Array<Cocktail>,
    fetchUserData: (user: any) => Promise<void>,
    fetchFavoriteCocktails: (userData: UserData | null) => Promise<void>,
    fetchPublishedRecipes: (user: any) => Promise<void>,
}

const defaultState = {
    userData: null,
    favoriteCocktails: [],
    publishedRecipes: [],
    fetchUserData: (user: any) => Promise,
    fetchFavoriteCocktails: (userData: UserData | null) => Promise,
    fetchPublishedRecipes: (user: any) => Promise
}

const ProfileContext = createContext<ProfileContext>(defaultState)

export default ProfileContext

This is my provider:

import ProfileContext from './ProfileContext'

import { query, where, getDocs, collection } from 'firebase/firestore'
import { db } from '../services/firebase.config'

import { Cocktail, UserData } from '../types'
import { useState } from 'react'


export default function ProfileContextProvider ({ children }: { children: any }) {
  

  const [userData, setUserData] = useState<UserData | null>(null)
  const [favoriteCocktails, setFavoriteCocktails] = useState<Array<Cocktail>>([])
  const [publishedRecipes, setPublishedRecipes] = useState<Array<Cocktail>>([])

  const fetchUserData = async (user: any) => {
    try {
      const q = query(collection(db, 'mixrUsers'), where('email', '==', user.email)) 
      const querySnapshot = await getDocs(q)
      const result: any[] = []
      querySnapshot.forEach(doc => result.push(doc.data()) )
      setUserData(result[0])      
    } catch (err) {
      console.error(err)
    }
  }

  const fetchFavoriteCocktails = async (userData: UserData | null) => {
    try {
      const q = query(collection(db, 'mixrCocktails'), where('id', 'in', userData?.favoriteCocktails))   
      const querySnapshot = await getDocs(q)
      const result: any[] = []
      querySnapshot.forEach(doc => {result.push(doc.data())} )
      setFavoriteCocktails(result)

    } catch (err) {
      console.error(err)
    }
  }

  const fetchPublishedRecipes = async (user: any) => {
    try {
      console.log('fetchPublishedRecipes')
      const q = query(collection(db, 'mixrCocktails'), where('publisherId', '==', user?.uid))   
      const querySnapshot = await getDocs(q)
      const result: any[] = []
      querySnapshot.forEach(doc => result.push(doc.data()) )
      setPublishedRecipes(result)      
    } catch (err) {
      console.error(err)
    }
  }

  return (
    <ProfileContext.Provider value={{ userData, favoriteCocktails, publishedRecipes, fetchUserData, fetchFavoriteCocktails, fetchPublishedRecipes  }} >
          {children}
      </ProfileContext.Provider>
  )
}

And I'm consuming it on different components in the following ways:

import { useEffect, useContext } from 'react'
import ProfileContext from '../context/ProfileContext'

import { RootTabScreenProps } from '../types'

import { useAuthState } from 'react-firebase-hooks/auth'
import { auth } from '../services/firebase.config'

import { StyleSheet, Text, View, SafeAreaView, ScrollView, Image, TouchableOpacity } from 'react-native'
import CocktailCard from '../components/home/cocktailCard/CocktailCard'
import GenericAvatar from '../assets/images/genericAvatar.jpg'

export default function ProfileScreen({ navigation }: RootTabScreenProps<'Profile'>) {
  
  /* TODO - Profile context functions break */
  const [user] = useAuthState(auth as any)

  const {userData, favoriteCocktails, publishedRecipes, fetchUserData, fetchFavoriteCocktails, fetchPublishedRecipes} = useContext(ProfileContext)

  useEffect(() => {
    fetchUserData(user)
    fetchPublishedRecipes(user)
  }, [publishedRecipes])

  useEffect(() => { if(userData) fetchFavoriteCocktails(userData) },[userData, favoriteCocktails])
  /* ====================================== */

  return (
    <SafeAreaView style={styles.container}>
      <ScrollView>

          <View style={styles.profileHeader}>
            <Image style={styles.profilePicture} source={user ? { uri: user?.photoURL } : GenericAvatar}/>
            <Text style={styles.profileName}>{user?.displayName}</Text>
          </View>
          ...
import { useContext, useEffect } from 'react'

import ProfileContext from '../context/ProfileContext'

import { RootTabScreenProps } from '../types'

import { useAuthState } from 'react-firebase-hooks/auth'
import { auth } from '../services/firebase.config'
import { Text, StyleSheet, View } from 'react-native'

import CocktailCard from '../components/home/cocktailCard/CocktailCard'

export default function PublishedRecipesScreen({ navigation }: RootTabScreenProps<'Published recipes'>) {

/* TODO - Profile context functions break */
const [user] = useAuthState(auth as any)

const { publishedRecipes, fetchPublishedRecipes } = useContext(ProfileContext)

useEffect(() => {fetchPublishedRecipes(user)}, [])
/* ====================================== */

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Published recipes</Text>

      <View style={styles.cardsContainer}>
        {publishedRecipes.map((cocktail, i) => <CocktailCard key={i} cocktail={cocktail} navigation={navigation} />)}
      </View>
      ...

I understand there is a conflict between the types I'm declaring on the context file, and the actual types the functions are returning. But I don't understand how should I declare them.

This functions fetch data from a DB and update a state, so they don't return anything.

I've tried fetchUserData: (user: any) => void, but I get Expression expected.ts(1109)

What's the right way to type this kind of functions?

Full code can be found here: https://github.com/coccagerman/mixr/

Thanks in advance!

CodePudding user response:

Here it is not a type :

const defaultState = {
    fetchUserData: (user: any) => Promise,
    fetchFavoriteCocktails: (userData: UserData | null) => Promise,
    fetchPublishedRecipes: (user: any) => Promise
}

You must provide some default values, so some async functions that return nothing will do :

const defaultState = {
    fetchUserData: async (user: any) => {},
    fetchFavoriteCocktails: async (userData: UserData | null) => {},
    fetchPublishedRecipes: async (user: any) => {}
}
  • Related