Home > Software design >  TypeScript says return type of function is either `A | B`, but by passing arguments it must be A sin
TypeScript says return type of function is either `A | B`, but by passing arguments it must be A sin

Time:11-18

I have implemented client function that accepts an optional formatter parameter which is either function or undefined, even though I'm checking if the formatter is undefined or not, and return the result based on that inside client, but TS seems to ignore the check and show the return value wrong. What am I missing in here?

Function implementation:

import axios from "axios";
import { Required } from "utility-types";
import { AxiosRequestConfig } from "axios";

const client = async <D, F>(
    { url, ...config }: Required<AxiosRequestConfig, "url">,
    formatter?: (data: D) => F
) => {
    const response = await axios.request<D>({
        url: encodeURI(url),
        ...config,
    });
    if (typeof formatter === "undefined") {
        return response;
    }
    return formatter(response.data);
};

Usage:

const formatHomeData = (data: HomePageResponse) => {
    return {
        products: data.result.bestSell, // This will result in an Array
        slides: data.result.sliders,
        notification: data.result.alert,
    };
};

export const fetchIndex = async () => {
    const url = 'http://localhost:4000/home';
    const formatted = await client({ url }, formatHomeData);
    const products = formatted.products; // TS complains here
};

fetchIndex();

TS Error

Property 'products' does not exist on type '{ products: FormattedProduct[]; slides: SliderItemResponse[]; notification: string; } | AxiosResponse<HomePageResponse>'.
Property 'products' does not exist on type 'AxiosResponse<HomePageResponse>'.ts(2339)

By the way, I'm using Generics, so that I don't have to keep defining the return type of formatter functions explicitly each time like this:

interface FormatHomeDataReturnType {
    products: FormattedProduct[];
    slides: SliderItemResponse[];
    notification: string;
}

Here's a link to TypeScript Playground in the minimalist way I could make it.

CodePudding user response:

It would be really nice if Typescript could infer this automatically, but it's not so automatic. It does give you a way to specify it explicitly using overloads though.

Overloads are a way to define the return type for different variations of a function signature. For a generic async lambda like in your example, this looks like:

type ClientOverload = {
  <D, F>(urlAndConfig: Required<AxiosRequestConfig, 'url'>): Promise<AxiosResponse<D>>;
  <D, F>(urlAndConfig: Required<AxiosRequestConfig, 'url'>, formatter: (data: D) => F): Promise<F>;
};
const client: ClientOverload = async <D, F>(...) { ... };

If you add the overload explicitly stating how defining formatting affects the return type, you won't get an error trying to use the products property.

  • Related