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