Home > Enterprise >  Incorrect return type in function overloading without function arguments in TypeScript
Incorrect return type in function overloading without function arguments in TypeScript

Time:03-30

I have a function that can return objects with different values depending on certain conditions. This function doesn't receive any arguments.

I wrote two overloads that describe different return types, but when I use this function and try to destruct values from the object I always receive a value that is defined on the first overload even if it doesn't fit the conditions.

Snippet:

const params = {
  brandId: 1, // could be undefined
  productId: 2, // could be undefined
  centerId: 3, // could be undefined
};

type ValidParams = {
  isValid: boolean;
  params: {
    brandId: number;
    productId: number;
    centerId: number;
  };
};

type InvalidParams = {
  isValid: boolean;
  params: {
    brandId: undefined;
    productId: undefined;
    centerId: undefined;
  };
};

export function useParams(): ValidParams;
export function useParams(): InvalidParams;

export function useParams() {
  const { brandId, productId, centerId } = params;

  if (brandId && productId && centerId) {
    return {
      isValid: true,
      params: {
        brandId,
        productId,
        centerId,
      },
    };
  } else {
    return {
      isValid: false,
      params: {
        brandId: undefined,
        productId: undefined,
        centerId: undefined,
      },
    };
  }
}

function someFunction() {
  const {
    isValid,
    params: { brandId, productId, centerId },
  } = useParams();

  if (isValid) {
    const id = brandId;
  }
}

I expect that if isValid value is true, then all of the params are numbers, and in case that isValid is false, then params are undefined.

e.g:

if (isValid) {
  brandId, productId, centerId // all are numbers
}

Link to TS playground

TS version: 4.6.3

Much appreciate any help on this)

CodePudding user response:

You can pass params object as an argument to useParams function. So that you have some parameters to overload.

Also make isValid a literal type for both the interfaces

Something like this:

const params = {
  brandId: undefined, // could be undefined
  productId: undefined, // could be undefined
  centerId: undefined, // could be undefined
};

type ValidParams = {
  isValid: true;
  params: {
    brandId: number;
    productId: number;
    centerId: number;
  };
};

type InvalidParams = {
  isValid: false;
  params: {
    brandId: undefined;
    productId: undefined;
    centerId: undefined;
  };
};

// accept params as an argument so that you have some parameters to overload
export function useParams(_params: {
  brandId: undefined;
  productId: undefined;
  centerId: undefined;
}): InvalidParams;
export function useParams(_params: {
  brandId: number;
  productId: number;
  centerId: number;
}): ValidParams;
export function useParams(_params: {
  brandId: number | undefined;
  productId: number | undefined;
  centerId: number | undefined;
}) {
  const { brandId, productId, centerId } = _params;

  if (brandId && productId && centerId) {
    return {
      isValid: true,
      params: {
        brandId,
        productId,
        centerId,
      },
    };
  } else {
    return {
      isValid: false,
      params: {
        brandId: undefined,
        productId: undefined,
        centerId: undefined,
      },
    };
  }
}

const {
  isValid,
  params: { brandId, productId, centerId },
} = useParams(params); // pass params as an argument

if (isValid && brandId && productId && centerId) {
  const id: number = brandId;
}

In this case, I believe you do not need the isValid flag, so you can remove it as follows.

const params = {
  brandId: 1, // could be 1
  productId: 1, // could be 1
  centerId: 1, // could be 1
};

type ValidParams = {
  params: {
    brandId: number;
    productId: number;
    centerId: number;
  };
};

type InvalidParams = {
  params: {
    brandId: undefined;
    productId: undefined;
    centerId: undefined;
  };
};

// accept params as an argument so that you have some parameters to overload
export function useParams(_params: {
  brandId: undefined;
  productId: undefined;
  centerId: undefined;
}): InvalidParams;
export function useParams(_params: {
  brandId: number;
  productId: number;
  centerId: number;
}): ValidParams;
export function useParams(_params: {
  brandId: number | undefined;
  productId: number | undefined;
  centerId: number | undefined;
}) {
  const { brandId, productId, centerId } = _params;

  if (brandId && productId && centerId) {
    return {
      params: {
        brandId,
        productId,
        centerId,
      },
    };
  } else {
    return {
      params: {
        brandId: undefined,
        productId: undefined,
        centerId: undefined,
      },
    };
  }
}

const {
  params: { brandId, productId, centerId },
} = useParams(params); // pass params as an argument

if (brandId && productId && centerId) {
  const id: number = brandId;
}

It's still a valid code. If you pass {brandId: 1, productId: 1, centerId: 1} as an argument to useParams, you will get brandId && productId && centerId as true because overload will infer all as a number type. and same for undefined type.

This is still a valid code. If you pass {brandId: 1, productId: 1, centerId: 1} as arguments to useParams, you will get brandId && productId && centerId as true since overflow infers each as a 'number' type. The same applies to undefined types.

CodePudding user response:

One simple way to handle this is to define your return types a little differently as well as indicate that the function can return one or the other with a Discriminated Union.

In this code, you need to test for isValid being true before the compiler will let you access the values. I've also tweaked the test to check explicitly for undefined as a 0 value would also be "not there". You can keep the "truthy number" check if 0 is not a valid value, but you explicitly call out that it could be undefined without indicating that 0 is also not valid.

const params = {
  brandId: 1, // could be undefined
  productId: 2, // could be undefined
  centerId: undefined, // could be undefined
};

type ValidParams = {
  isValid: true;
  params: {
    brandId: number;
    productId: number;
    centerId: number;
  };
};

type InvalidParams = {
  isValid: false;
};

function useParams(): ValidParams | InvalidParams {
  const { brandId, productId, centerId } = params;

  if (brandId != undefined && productId != undefined && centerId != undefined) {
    return {
      isValid: true,
      params: {
        brandId,
        productId,
        centerId,
      },
    };
  } else {
    return {
      isValid: false,
    };
  }
}

function someFunction() {
  // cannot destructure the params here, because we don't
  // know yet if it is valid or invalid type
  const p = useParams();
  if (p.isValid) {
    // we know now we have the params values,
    // so we can access, destructure, etc.
    const id = p.params.brandId;
  }
  console.dir(p);
}

someFunction();

TypeScript Playground

  • Related