The Web Share API opens the device's native share sheet for text, links, and supported files. Implement a React hook that detects navigator.share, optionally checks data with navigator.canShare, and exposes one stable method for starting the operation. Your hook must remain safe when rendered outside a supporting browser.
type ShareData = {
title?: string;
text?: string;
url?: string;
files?: File[];
};
function useWebShare(): {
isSupported: boolean;
canShare: (data: ShareData) => boolean;
share: (data: ShareData) => Promise<void>;
};
const { isSupported, canShare, share } = useWebShare();
isSupported; // true when navigator.share is a function
canShare({ text: 'Read this' }); // navigator.canShare's boolean, when available
// Call from a click handler to preserve transient user activation.
await share({ title: 'UIReady', url: 'https://uiready.dev' });
// navigator.share exists, but navigator.canShare does not:
canShare({ text: 'Hello' }); // true; the native call performs final validation
// navigator.canShare returns false:
await share({ files: [] }); // rejects without calling navigator.share
isSupported without reading navigator, then reconcile it in an effect after mount.canShare and share must read navigator when invoked while keeping stable function identities across rerenders.navigator.share, including the user's AbortError cancellation.web-share Permissions Policy, and transient activation. Call share directly from a click or similar user action.You expose the native share sheet without letting a browser-only API make rendering unsafe.
A Share button has two jobs: decide whether the browser offers sharing, then pass the requested data to the native operation. Some browsers expose navigator.share without navigator.canShare, so validation is optional even though sharing itself is available. The hook must also preserve browser rejections because cancellation is useful information to the caller.
Treat the hook as a thin gate in front of the browser. Rendering records whether the entrance exists; calling a method reads the browser again, validates when possible, and then hands the original object to the native share sheet.
function useWebShare() {
const isSupported = typeof navigator.share === 'function';
return {
isSupported,
canShare: (data) => navigator.canShare(data),
share: (data) => navigator.share(data),
};
}
This reads navigator during rendering, which fails in a non-browser environment. It also assumes canShare exists whenever share does. The two inline functions change identity on every render, and the methods capture whatever the browser exposed during that render instead of reading it when the user clicks.
const { useCallback, useEffect, useState } = require('react');
function getNavigator() {
return typeof navigator === 'undefined' ? null : navigator;
}
function useWebShare() {
const [isSupported, setIsSupported] = useState(false);
useEffect(() => {
const nav = getNavigator();
setIsSupported(typeof nav?.share === 'function');
}, []);
const canShare = useCallback((data) => {
const nav = getNavigator();
if (typeof nav?.share !== 'function') return false;
// A missing validator does not make the native share method unusable.
if (typeof nav.canShare !== 'function') return true;
return Boolean(nav.canShare(data));
}, []);
const share = useCallback(async (data) => {
const nav = getNavigator();
if (typeof nav?.share !== 'function') {
throw new Error('Web Share API is not supported');
}
if (typeof nav.canShare === 'function' && !nav.canShare(data)) {
throw new Error('The provided data cannot be shared');
}
// Awaiting preserves the browser's resolve timing and rejection object.
await nav.share(data);
}, []);
return { isSupported, canShare, share };
}
module.exports = { useWebShare };
getNavigator makes every access environment-safe. The effect reconciles support after mount, while useCallback keeps both public methods stable. Each callback reads the current methods at call time, so a late polyfill or changed browser capability is respected without rebuilding the callbacks.
A user clicks Share with { title: 'UIReady', url: 'https://uiready.dev' }. The stable share callback reads the current navigator.share. If navigator.canShare exists, the hook passes that exact object to it; false stops the operation with a clear error. A true result reaches navigator.share, and the hook waits until its promise resolves. If the user cancels and the browser rejects with AbortError, the same error reaches the caller.
navigator.share requires transient activation, so invoke share directly inside the click handler that received the user's action.AbortError when the user closes the sheet, so let that native rejection reach the caller.canShare exists — support for share is the required gate; fall back to the native call when prevalidation is unavailable.DOMException details, so only create errors for the hook's own preflight failures.isSupported is false, while keeping that fallback outside this browser-API hook.share in a consuming component when the interface needs pending, success, or cancellation feedback.canShare({ files }) before combining it with text fields, following the platform's file-sharing guidance.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
The Web Share API opens the device's native share sheet for text, links, and supported files. Implement a React hook that detects navigator.share, optionally checks data with navigator.canShare, and exposes one stable method for starting the operation. Your hook must remain safe when rendered outside a supporting browser.
type ShareData = {
title?: string;
text?: string;
url?: string;
files?: File[];
};
function useWebShare(): {
isSupported: boolean;
canShare: (data: ShareData) => boolean;
share: (data: ShareData) => Promise<void>;
};
const { isSupported, canShare, share } = useWebShare();
isSupported; // true when navigator.share is a function
canShare({ text: 'Read this' }); // navigator.canShare's boolean, when available
// Call from a click handler to preserve transient user activation.
await share({ title: 'UIReady', url: 'https://uiready.dev' });
// navigator.share exists, but navigator.canShare does not:
canShare({ text: 'Hello' }); // true; the native call performs final validation
// navigator.canShare returns false:
await share({ files: [] }); // rejects without calling navigator.share
isSupported without reading navigator, then reconcile it in an effect after mount.canShare and share must read navigator when invoked while keeping stable function identities across rerenders.navigator.share, including the user's AbortError cancellation.web-share Permissions Policy, and transient activation. Call share directly from a click or similar user action.You expose the native share sheet without letting a browser-only API make rendering unsafe.
A Share button has two jobs: decide whether the browser offers sharing, then pass the requested data to the native operation. Some browsers expose navigator.share without navigator.canShare, so validation is optional even though sharing itself is available. The hook must also preserve browser rejections because cancellation is useful information to the caller.
Treat the hook as a thin gate in front of the browser. Rendering records whether the entrance exists; calling a method reads the browser again, validates when possible, and then hands the original object to the native share sheet.
function useWebShare() {
const isSupported = typeof navigator.share === 'function';
return {
isSupported,
canShare: (data) => navigator.canShare(data),
share: (data) => navigator.share(data),
};
}
This reads navigator during rendering, which fails in a non-browser environment. It also assumes canShare exists whenever share does. The two inline functions change identity on every render, and the methods capture whatever the browser exposed during that render instead of reading it when the user clicks.
const { useCallback, useEffect, useState } = require('react');
function getNavigator() {
return typeof navigator === 'undefined' ? null : navigator;
}
function useWebShare() {
const [isSupported, setIsSupported] = useState(false);
useEffect(() => {
const nav = getNavigator();
setIsSupported(typeof nav?.share === 'function');
}, []);
const canShare = useCallback((data) => {
const nav = getNavigator();
if (typeof nav?.share !== 'function') return false;
// A missing validator does not make the native share method unusable.
if (typeof nav.canShare !== 'function') return true;
return Boolean(nav.canShare(data));
}, []);
const share = useCallback(async (data) => {
const nav = getNavigator();
if (typeof nav?.share !== 'function') {
throw new Error('Web Share API is not supported');
}
if (typeof nav.canShare === 'function' && !nav.canShare(data)) {
throw new Error('The provided data cannot be shared');
}
// Awaiting preserves the browser's resolve timing and rejection object.
await nav.share(data);
}, []);
return { isSupported, canShare, share };
}
module.exports = { useWebShare };
getNavigator makes every access environment-safe. The effect reconciles support after mount, while useCallback keeps both public methods stable. Each callback reads the current methods at call time, so a late polyfill or changed browser capability is respected without rebuilding the callbacks.
A user clicks Share with { title: 'UIReady', url: 'https://uiready.dev' }. The stable share callback reads the current navigator.share. If navigator.canShare exists, the hook passes that exact object to it; false stops the operation with a clear error. A true result reaches navigator.share, and the hook waits until its promise resolves. If the user cancels and the browser rejects with AbortError, the same error reaches the caller.
navigator.share requires transient activation, so invoke share directly inside the click handler that received the user's action.AbortError when the user closes the sheet, so let that native rejection reach the caller.canShare exists — support for share is the required gate; fall back to the native call when prevalidation is unavailable.DOMException details, so only create errors for the hook's own preflight failures.isSupported is false, while keeping that fallback outside this browser-API hook.share in a consuming component when the interface needs pending, success, or cancellation feedback.canShare({ files }) before combining it with text fields, following the platform's file-sharing guidance.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.