Handling embeds with scripts vs. direct iframes #340
-
Wanted to see if there is a recommended path (or examples?) for handling embeds where third parties are injecting scripts. I'm trying to add embeds that would handle rendering Twitter statuses. Basically you can exchange a tweet URL and get HTML back see here, but this isn't an <iframe>, it's a blockquote with a separate script that then converts into an iframe. I'm just running into an issue where the actual blockquote is rendered, but the script is never executed to convert it into a widget. <!-- fallback content -->
<blockquote class="twitter-tweet">
<p lang="en" dir="ltr">Sports bar broadcasting CNN on its street-facing screens. A 2020 thing</p>— mattie kahn (@mattiekahn)
<a href="https://twitter.com/mattiekahn/status/1337173064098967552?ref_src=twsrc%5Etfw">December 10, 2020</a>
</blockquote>
<!-- script -->
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> If helpful, this is the import { useEffect, useState } from "react"
import { Box, Icon } from "components/ui"
import { get } from "utils/fetch"
import { SiTwitter } from "react-icons/si"
const URL_REGEX = /(http|https)?:\/\/(www\.)?twitter.com\/(\w+)\/status(es)?\/[0-9]+/
type Props = {
attrs: {
href: string;
matches: string[];
}
}
export default function Twitter({ attrs }: Props) {
const { href } = attrs
// fetch and set html content
const [html, setHtml] = useState(null)
useEffect(() => {
async function fetch() {
try {
const response = await get(`/api/embed/twitter?url=${href}`)
setHtml(response.html)
} catch (error) {
console.log(error)
}
}
if (href) {
fetch()
}
}, [href])
const loadingMarkup = (
<Box className="loader-box" border="1px solid #dfe3e8" padding="1em" borderRadius="5px">
<Icon source={SiTwitter} boxSize="32px" color="#dfe3e8" />
<p>Loading...</p>
</Box>
)
return html ? (
<Box dangerouslySetInnerHTML={{__html: html}} />
) : loadingMarkup
}
Twitter.ENABLED = [URL_REGEX] |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
The useEffect isn't being called? Nothing jumps out as a clear reason this wouldn't work |
Beta Was this translation helpful? Give feedback.
-
Turns out you need to force a script reload if you are dynamically inserting these embeds. Via Twitter docs: If content is dynamically inserted into a page (such as lazy-loading content or using a pushState technique to navigate between articles) it’s necessary to parse new buttons and widgets using the twttr.widgets.load() function. TikTok follows a similar pattern, but they haven't exposed a similar method to reload the widgets. Adding where I landed below in case anyone else runs into this. // Twitter.tsx
import { useEffect, useState } from "react"
import { Box, Icon } from "components/ui"
import { get } from "utils/fetch"
import { SiTwitter } from "react-icons/si"
import { classNames } from "@shopify/css-utilities"
import { injectScript } from "./injectScript"
// Example link:
// https://twitter.com/emilypothast/status/1336877515915153408?s=20
//
const URL_REGEX = /https:\/\/(www\.)?twitter.com\/(\w+)\/status(es)?\/([0-9]+)/
type Props = {
isSelected: boolean;
attrs: {
href: string;
matches: string[];
}
}
export default function Twitter({ attrs, isSelected }: Props) {
const { href } = attrs
// fetch and set html content
const [html, setHtml] = useState(null)
useEffect(() => {
async function fetch() {
try {
const response = await get(`/api/embed/twitter?url=${href}`)
setHtml(response.html)
// force reload of the widget
window.twttr.widgets.load()
} catch (error) {
console.log(error)
}
}
if (href) {
injectScript({ id: "twitter-js", url: "https://platform.twitter.com/widgets.js" })
fetch()
}
}, [href])
const loadingMarkup = (
<Box className="loader-box" border="1px solid #dfe3e8" padding="1em" borderRadius="5px">
<Icon source={SiTwitter} boxSize="32px" color="#dfe3e8" />
<p>Loading...</p>
</Box>
)
const className = classNames(
isSelected && "ProseMirror-selectednode",
)
return html ? (
<Box
id="embed-twitter"
className={className}
dangerouslySetInnerHTML={{__html: html}}
/>
) : loadingMarkup
}
Twitter.ENABLED = [URL_REGEX]
// extend window object with twttr functions
declare global {
interface Window {
twttr:any;
}
} // injectScript.ts
type Props = {
id: string;
url: string;
/** Prevent duplicate injection of snippet */
preventDuplicate?: boolean;
}
export function injectScript({
id,
url,
preventDuplicate=true,
}: Props): HTMLElement {
/**
* When possible, we only inject this script once for a given embed type
* Certain platforms (see TikTok) don't expose a method to force refresh
* the iframe element
*
*/
if (preventDuplicate) {
const exitingScript = document.getElementById(id)
if (exitingScript) return exitingScript
}
const script = document.createElement("script")
script.setAttribute("src", url)
script.setAttribute("async", "true")
script.id = id
document.head.appendChild(script)
return script
} |
Beta Was this translation helpful? Give feedback.
Turns out you need to force a script reload if you are dynamically inserting these embeds. Via Twitter docs:
If content is dynamically inserted into a page (such as lazy-loading content or using a pushState technique to navigate between articles) it’s necessary to parse new buttons and widgets using the twttr.widgets.load() function. TikTok follows a similar pattern, but they haven't exposed a similar method to reload the widgets.
Adding where I landed below in case anyone else runs into this.