Yair Rodríguez
Yair Rodríguez

Reputation: 63

What is the appropriate way to return a variable with a different type than the variable being reduced?

I have the following code:

interface ICartItemResponse {
    sku: string;
    priceChange: boolean;
    price: number;
    total: number;
    label: string;
}

type TCartTotalType = "shipping" | "tax" | "discount";

interface ICartTotal {
    type: TCartTotalType;
    title: string;
    price: number;
};

const promo: ICartItemResponse[] = [/* Some data */];

const availablePromos: ICartTotal[] = promo.reduce((acc, cur) => {
    return [
        ...acc,
        {
            type: "discount",
            title: cur.label,
            price: cur.total
        }
    ]
}, []);

The code above throws two erros:

  1. In the availablePromos definition:
const availablePromos: ICartTotal[]
Type 'ICartItemResponse' is missing the following properties from type 'ICartTotal[]': length, pop, push, concat, and 29 more.
  1. In the previous and current values (acc and cur, respectively):
No overload matches this call.
  Overload 1 of 3, '(callbackfn: (previousValue: ICartItemResponse, currentValue: ICartItemResponse, currentIndex: number, array: ICartItemResponse[]) => ICartItemResponse, initialValue: ICartItemResponse): ICartItemResponse', gave the following error.
    Argument of type '(acc: never[], cur: ICartItemResponse) => { type: "discount"; title: string; price: number; }[]' is not assignable to parameter of type '(previousValue: ICartItemResponse, currentValue: ICartItemResponse, currentIndex: number, array: ICartItemResponse[]) => ICartItemResponse'.
      Types of parameters 'acc' and 'previousValue' are incompatible.
        Type 'ICartItemResponse' is missing the following properties from type 'never[]': length, pop, push, concat, and 29 more.
  Overload 2 of 3, '(callbackfn: (previousValue: never[], currentValue: ICartItemResponse, currentIndex: number, array: ICartItemResponse[]) => never[], initialValue: never[]): never[]', gave the following error.
    Argument of type '(acc: never[], cur: ICartItemResponse) => { type: "discount"; title: string; price: number; }[]' is not assignable to parameter of type '(previousValue: never[], currentValue: ICartItemResponse, currentIndex: number, array: ICartItemResponse[]) => never[]'.
      Type '{ type: "discount"; title: string; price: number; }[]' is not assignable to type 'never[]'.
        Type '{ type: "discount"; title: string; price: number; }' is not assignable to type 'never'.

I notice it was possible to "solve" both errors in two different ways:

  1. Specifying the type of previous value but not of the current value
(acc: ICartTotal[], cur)

Notice that if I specify the type of the current value (which is indeed being infered) with the same infered type. Throws the following error:

No overload matches this call.
  Overload 1 of 3, '(callbackfn: (previousValue: ICartItemResponse, currentValue: ICartItemResponse, currentIndex: number, array: ICartItemResponse[]) => ICartItemResponse, initialValue: ICartItemResponse): ICartItemResponse', gave the following error.
    Argument of type '(acc: ICartTotal[], cur: ICartItemResponse) => { type: string; title: string; price: number; }[]' is not assignable to parameter of type '(previousValue: ICartItemResponse, currentValue: ICartItemResponse, currentIndex: number, array: ICartItemResponse[]) => ICartItemResponse'.
      Types of parameters 'acc' and 'previousValue' are incompatible.
        Type 'ICartItemResponse' is missing the following properties from type 'ICartTotal[]': length, pop, push, concat, and 29 more.
  Overload 2 of 3, '(callbackfn: (previousValue: ICartTotal[], currentValue: ICartItemResponse, currentIndex: number, array: ICartItemResponse[]) => ICartTotal[], initialValue: ICartTotal[]): ICartTotal[]', gave the following error.
    Argument of type '(acc: ICartTotal[], cur: ICartItemResponse) => { type: string; title: string; price: number; }[]' is not assignable to parameter of type '(previousValue: ICartTotal[], currentValue: ICartItemResponse, currentIndex: number, array: ICartItemResponse[]) => ICartTotal[]'.
      Type '{ type: string; title: string; price: number; }[]' is not assignable to type 'ICartTotal[]'.
        Type '{ type: string; title: string; price: number; }' is not assignable to type 'ICartTotal'.
          Types of property 'type' are incompatible.
            Type 'string' is not assignable to type 'ICartTotalType'.
  1. Asserting the type of initial value
[] as ICarttotal[]

This one works fine, but if I also combine this with the first approach, throws the same error as in the last point.

My question is: In order to return a variable with a different type than the variable being reduced, is it only necessary to assert the type of the initial value on a reducer if the arguments are already being infered?

Thank you in advance!

Upvotes: 0

Views: 170

Answers (1)

Alex Wayne
Alex Wayne

Reputation: 187262

With reduce I often find it best to explicitly pass the result type as the generic type parameter. Then the initial argument and the callback function all get typed properly for you. I find this often removes a great deal of the type errors you usually get with calling reduce because Typescript knows exactly how the completed reduce result is typed before it starts.

const availablePromos = promo.reduce<ICartTotal[]>((acc, cur) => {
    return [
        ...acc,
        {
            type: "discount",
            title: cur.label,
            price: cur.total
        }
    ]
}, [])
// availablePromos is ICartTotal[]

See playground

Upvotes: 2

Related Questions