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
}
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();