Update for TS4.4:
TypeScript will soon allow control flow analysis of aliased conditions, meaning that saving a conditional like isShareable as a const may allow it to be used later as a type guard. Unfortunately the code here seems to have deteriorated a bit, since share is a known property of Navigator and you can't just merge it in anymore.
Aliased conditions like isShareable will only work if the properties you're testing are readonly, since for all the compiler knows, someone might set window.navigator.share = undefined at some point after you set isShareable but before you call window.navigator.share. This is quite a stumbling block if you're not in control of whether or not things are readonly. Still, if it looked like this:
interface Navigator extends Omit<typeof window.navigator, "share"> {
readonly share?: (data?: ShareData) => Promise<void>;
}
declare const _window: Omit<typeof window, 'navigator'> &
{ readonly navigator?: Navigator }
Then in TypeScript 4.4, this will not be an error:
const isShareable =
(_window.navigator && _window.navigator.share);
const handleShareStored = async () => {
if (isShareable) {
try {
await _window.navigator.share({ // no error in TS4.4+
text: "Checkout Verishop!",
title: "Share Verishop",
url: "referralUrl"
});
} catch (e) {
_window.alert(e);
}
}
};
Playground link to code
Pre-TS4.4 answer:
TypeScript doesn't currently let you save type guard results in a variable to be used later the way you're trying to do with isShareable. There are two open suggestions asking for this in TypeScript's GitHub issues: microsoft/TypeScript#12184 and microsoft/TypeScript#24865. Neither of these issues seem to have much traction, but I suppose you can go to those issues and give them a 👍 or describe your use case if it's compelling. The problem is likely to be that it's not a trivial thing to implement and still maintain compiler performance.
What can you do right now? The obvious and unsatisfying answer is not to try to do your type guarding ahead of time, but do it right before you want to use the guarded window.navigator.share function:
// just do the check inline
const handleShareExplicit = async () => {
if (window.navigator && window.navigator.share) {
try {
await window.navigator.share({ // no error
text: "Checkout Verishop!",
title: "Share Verishop",
url: "referralUrl"
});
} catch (e) {
window.alert(e);
}
}
};
That's not really all that bad because it's still fairly terse... (window.navigator && window.navigator.share) still fits on one line. But some people might have a similar issue where the type guard is a much more elaborate check, or you might be philosophically opposed to doing your check more than once. In that case, you could try a different approach:
// store a reference to the function if it exists
const share =
window.navigator && window.navigator.share
? window.navigator.share // .bind(window.navigator) ?? see note below
: undefined;
const handleShare = async () => {
if (share) {
try {
await share({ // no error
text: "Checkout Verishop!",
title: "Share Verishop",
url: "referralUrl"
});
} catch (e) {
window.alert(e);
}
}
};
Instead of storing a boolean-like variable isShareable, store the equivalent variable share whose type is ((data?: ShareData) => Promise<void>) | undefined. Then the check if (share) { share(...) } has the same semantics as what you were doing, except that in this case the compiler can easily see that you are using the thing you just checked.
(Do note that depending on how window.navigator.share() is implemented, it may need the this context to be window.navigator. If so, then you might want save share as window.navigator.share.bind(window.navigator) instead of just window.navigator.share. It probably doesn't hurt to do that in any case. It's up to you.)
Okay, hope that helps. Good luck!
Link to code