How to set iframe content of a react component
Solution 1:
TLDR;
If you're looking for a way to control the contents of an <iframe>
via React in a de-facto canonical way, Portals are the way to go.
And as with all things Portal: Once you establish a reference to an existing and mounted DOM node (in this case that would be the contentWindow of a given <iframe>
) and
create a Portal with it, its contents are also considered children of the «parent» virtual DOM, which means a shared (synthetic) event system, contexts and so on.
Please note that, for code brevity, the examples below make use of the Optional chaining operator, which as of this writing is not supported in all browsers.
Example: A functional React component including hooks:
// iframe.js
import React, { useState } from 'react'
import { createPortal } from 'react-dom'
export const IFrame = ({
children,
...props
}) => {
const [contentRef, setContentRef] = useState(null)
const mountNode =
contentRef?.contentWindow?.document?.body
return (
<iframe {...props} ref={setContentRef}>
{mountNode && createPortal(children, mountNode)}
</iframe>
)
}
Example: A React class component:
// iframe.js
import React, { Component } from 'react'
import { createPortal } from 'react-dom'
export class IFrame extends Component {
constructor(props) {
super(props)
this.state = {
mountNode: null
}
this.setContentRef = (contentRef) => {
this.setState({
mountNode: contentRef?.contentWindow?.document?.body
})
}
}
render() {
const { children, ...props } = this.props
const { mountNode } = this.state
return (
<iframe
{...props}
ref={this.setContentRef}
>
{mountNode && createPortal(children, mountNode)}
</iframe>
)
}
}
Usage:
import { IFrame } from './iframe'
const MyComp = () => (
<IFrame>
<h1>Hello Content!</h1>
</IFrame>
)
Further control, for example over an <iframe>
s <head>
contents, can easily be achieved as
this Gist shows.
There is also react-frame-component, a package that imho offers pretty much
everything you need when working with controlled <iframe>
s in React.
Caveats:
- This answer only addresses use cases, where the owner of a given
<iframe>
wants to programmatically control (as in deciding about) its contents in a React-ish way. - This answer assumes, that the owner of an
<iframe>
complies with the Same-origin policy. - This answer is not suited to track how and when external resources are loaded in an
<iframe src="https://www.openpgp.org/>
kind of scenario. - If accessibility is something you care about, you should give your iframes meaningful title attributes.
Use cases (that I know of);
- The OP's use case: Ads and the need to control how and when those can access a safely scoped element on your website.
- Embeddable third-party widgets.
- My use case (and hence my somewhat informed stance on the matter): CMS UI's, where you want to enable users to preview scoped CSS styles, including applied media queries.
Adding a given set of CSS styles (or stylesheets) to a controlled <iframe>
:
As one comment author pointed out, managing styles between a parent application and the contents of a controlled <iframe>
can be quite tricky.
If you're lucky enough to have (a) dedicated CSS file(s) incorporating all necessary visual instructions for your <iframe>
, it might suffice to just
pass your IFrame
component a <link>
tag referencing said style(s), even though this is not the most standard compliant way to go about <link>
refs:
const MyComp = () => (
<Frame>
<link rel="stylesheet" href="my-bundle.css">
<h1>Hello Content!</h1>
</Frame>
)
In this day and age, however, and especially in a React world, most of the time, build setups create styles and stylesheets on the fly: Because they leverage meta-languages like SASS or even more involved solutions like CSS-in-JS stuff (styled-components, emotion).
This sandbox contains examples of how to integrate some of the more popular styling strategies with iframes in React.
This answer used to also give recipes with regards to versions of React prior to 16.3. At this point in time, however, I think it's safe to say that most of us are able to pull off a React version including Portals, and, to a lesser extent, hooks. If you're in need of solutions with regards to iframes and React versions < 16, hit me up, I'll gladly offer advice.
Solution 2:
There is an easier solution if someone just wants to display small HTML inside the iframe.
<iframe src={"data:text/html,"+encodeURIComponent(content)}/>
The max length of content is 32768 characters.
There is also easy to use react-frame-component package which was mentioned in the accepted answer.