Share button using React, Web Share API and unsupported fallback
The Web Share API provides an easy way to trigger the native share menu.
Before getting started
There're a few things we should know before start coding.
First, browser support, and this is probably its main problem. Web Share API doesn't have the greatest support yet.
Web Share API on caniuse.com
However, it's supported on the main mobile browsers and we can add a fallback option to all the unsupported browsers.
Apart from that, some other limitations are:
- It can only be used on sites served over HTTPS.
- It must be triggered in response to some user action such as click.
Web Sharing API
To trigger the share menu we are gonna used the promise-based method .share()
. But, as I said before and because is not fully supported, let's check if It's supported on the browser first.
const onShareClick = (e) => {
e.preventDefault()
if (navigator.share) {
navigator
.share({
title: 'title',
text: 'Some message',
url: 'https://yourwebsite.com',
})
.catch(console.error)
}
}
Now, we can attached to the the onClick event of our component and pass the information via props.
const ShareButton = ({ title, text, url }) => {
const onShareClick = (event) => {
event.preventDefault()
if (navigator.share) {
navigator
.share({
title: title,
text: text,
url: url,
})
.catch(console.error)
}
}
return <button onClick={onShareClick}>Share</button>
}
Providing fallback
As I said earlier, let's add a second option for all the other browsers. On this case we'll add options for sharing on facebook, twitter and copy the url to the clipboard. Let's code that.
We can add a state and trigger it whenever navigator.share
is unsupported.
const ShareButton = ({ title, text, url }) => {
const [isShowed, setIsShowed] = useState(false)
const onShareClick = (e) => {
e.preventDefault()
if (navigator.share) {
navigator
.share({
title: title,
text: text,
url: url,
})
.catch(console.error)
} else {
setIsShowed((currentIsShowed) => !currentIsShowed)
}
}
return <button onClick={onShareClick}>Share</button>
}
Now, let's wrap our button into a div container and add the markup for the fallback.
const ShareButton = ({title, text, url}) => {
// ...
return (
<div>
<button onClick={onShareClick}>Share</button>
{isShowed ? (
<ul>
<li><button>Share on facebook</button></li>
<li><button>Share on twitter</button></li>
<li><button>Copy to the clipboad</button></li>
</ul>
) : null}
<div>
)
}
Share on facebook
For facebook there actually a lot of ways to do it, but some options seem a bit overwhelming. You can give it a look if you want to, but in this case I don't want to add the sdk because I don't need to track all the shares from the page, so we are gonna do it by using facebook.com/sharer
const ShareButton = ({title, text, url }) => {
// ...
const onFacebookShare = (e) => {
e.preventDefault()
window.open(
`https://www.facebook.com/sharer/sharer.php?u=${url}`,
'facebook-share-dialog',
'width=800,height=600'
)
}
return (
<div>
{ /* ... */}
{isShowed ? (
<ul>
<li>
<button onClick={onFacebookShare}>Share on facebook</button>
</li>
{ /* ... */}
</ul>
) : null}
<div>
)
}
Share on twitter
This one is actually really easy, just need to use an <a>
tag and handle it as an external link.
const ShareButton = ({title, text, url }) => {
// ...
return (
<div>
{ /* ... */}
{isShowed ? (
<ul>
{ /* ... */}
<li>
<a
target="_blank"
rel="noopener noreferrer"
href={`https://twitter.com/intent/tweet?url=${url}&text=${title}`}
>
Share on twitter
</a>
</li>
</ul>
) : null}
<div>
)
}
Copy to the clipboard
And finally, we are gonna use the navigator.clipboard
which is supported by most of the modern browsers.
Navigator.Clipboard
support on developr.mozilla.org
If you need support on internet explore you might want to explore other alternatives. For now, let's work with navigator.clipboard
and add some type of feedback for the users to know when it's been copied.
const ShareButton = ({title, text, url }) => {
const [isCopied, setIsCopied] = useState(false)
// ...
const onCopyToClipboard = (e) => {
e.preventDefault()
if (navigator.clipboard) {
navigator.clipboard
.writeText(url)
.then(() => setIsCopied(true))
.catch(console.error)
}
}
// Restarts the copy state after a few seconds
useEffect(() => {
if (!isCopied) return
const timer = setTimeout(() => {
setIsCopied(currentIsCopied => !currentIsCopied)
}, 3000)
return () => clearTimeout(timer)
}, [isCopied])
return (
<div>
{ /* ... */}
{isShowed ? (
<ul>
{ /* ... */}
<li>
<button onClick={onCopyToClipboard}>
{isCopied ? 'Copied!' : 'Copy to clipboard'}
</button>
</li>
</ul>
) : null}
<div>
)
}
Now, you only need to add some styles and that's it.
The full version of our component will look like this.
const ShareButton = ({title, text, url }) => {
const [isShowed, setIsShowed] = useState(false)
const [isCopied, setIsCopied] = useState(false)
const onShareClick = (e) => {
e.preventDefault()
if (navigator.share) {
navigator.share({
title: title,
text: text,
url: url,
})
.catch(console.error)
} else {
setIsShowed(currentIsShowed => !currentIsShowed)
}
}
const onFacebookShare = (e) => {
e.preventDefault()
window.open(
`https://www.facebook.com/sharer/sharer.php?u=${url}`,
'facebook-share-dialog',
'width=800,height=600'
)
}
const onCopyToClipboard = (e) => {
e.preventDefault()
if (navigator.clipboard) {
navigator.clipboard
.writeText(url)
.then(() => setIsCopied(true))
.catch(console.error)
}
}
useEffect(() => {
if (!isCopied) return
const timer = setTimeout(() => {
setIsCopied(currentIsCopied => !currentIsCopied)
}, 3000)
return () => clearTimeout(timer)
}, [isCopied])
return (
<div>
<button onClick={onShareClick}>Share</button>
{isShowed ? (
<ul>
<li>
<button onClick={onFacebookShare}>Share on facebook</button>
</li>
<li>
<a
target="_blank"
rel="noopener noreferrer"
href={`https://twitter.com/intent/tweet?url=${url}&text=${title}`}
>
Share on twitter
</a>
</li>
<li>
<button onClick={onCopyToClipboard}>
{isCopied ? 'Copied' : 'Copy to clipboard'}
</button>
</li>
</ul>
) : null}
<div>
)
}
Usage
<ShareButton
url="https://mywebsite.com/article-slug"
title="Article Title"
message="Check this article"
/>
One last thing
You might already notice is gonna be a bit annoying having to write the full url each time we use the component. We can change this by using window.location
and changing the url
prop's name to path
const ShareButton = ({ title, text, path }) => {
// Use a ternary for preventing bugs when is running on the server
const fullURL = window.location ? `${window.location.origin}${path}` : ''
// ...
return {
/* ... */
}
}
You'll need to replace all the url
variables with fullURL
Now, we can use our component and only pass it the path.
// You must use the leading slash for the path
<ShareButton
path="/article-slug"
title="Article Title"
message="Check this article"
/>