skip to Main Content

I have this hook:

import { userDB } from 'app/services/DBFunctions';
import { auth } from 'Fire';
import { collection, query, limit as fLimit, onSnapshot } from 'firebase/firestore';
import { IInvoice, IScheduledEmail, IScheduledInvoice, ITableData } from 'Interface';
import React, { useEffect, useState } from 'react';

interface Props {
    collectionPath: 'scheduled-invoices' | 'scheduled-emails'
}

const useScheduledFunctions = (props: Props) : IScheduledInvoice[] | IScheduledEmail[] => {
    const {collectionPath} = props
    const [scheduledFunctions, setScheduledFunctions] = useState<IScheduledInvoice[] | IScheduledEmail[]>([])
    const user = auth.currentUser

    useEffect(()=> {
        let queryCollection = query(
            collection(userDB(), collectionPath),
        )
        onSnapshot(queryCollection, (snap: any)=> {
            let scheduledFunctions = snap.docs.map((doc: any)=> doc.data())
            setScheduledFunctions(scheduledFunctions)
        })   
    }, [user, collectionPath])

    return scheduledFunctions
};

export default useScheduledFunctions;

But when I call it in a component, the type is either IScheduledInvoice or IScheduledEmail, is there a way to infer the type of the hook based on the collection path?
Currently when I call it, to avoid this conflict, I do this:

const scheduledEmails = useScheduledFunctions({collectionPath: 'scheduled-emails'}) as IScheduledEmail[]

But I dont like doing this and does not seem like a good solution as I am overriding the type.

2

Answers


  1. You can use overloads to pair certain argument types with return types, then provide an implementation that accepts and returns both.

    interface Props {
        collectionPath: 'scheduled-invoices' | 'scheduled-emails'
    }
    
    function useScheduledFunctions(props: { collectionPath: 'scheduled-invoices' }): IScheduledInvoice[]
    function useScheduledFunctions(props: { collectionPath: 'scheduled-emails' }): IScheduledEmail[]
    function useScheduledFunctions(props: Props): IScheduledInvoice[] | IScheduledEmail[] {
      return [] // implementation here
    }
    

    See Playground

    Login or Signup to reply.
  2. There you go: Playground

    interface Props {
      collectionPath: 'scheduled-invoices' | 'scheduled-emails'
    }
    
    interface IScheduledInvoice {}
    
    interface IScheduledEmail {}
    
    type Return<T> = T extends { collectionPath: 'scheduled-emails' } ? IScheduledEmail[] : IScheduledInvoice[];
    
    const useScheduledFunctions = <T extends Props>(props: T): Return<T> => {
      return []
    };
    
    const a = useScheduledFunctions({ collectionPath: 'scheduled-emails' });
    const b = useScheduledFunctions({ collectionPath: 'scheduled-invoices' });
    

    The pro with that solution is that you can add whatever properties in the interface Props. With overloads you will have to implement each possible arguments.

    Personal opinion: if you have more than two values for collectionPath I would go with overloads because you all have to nest ternary extends.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search