Preact Portal Example

by Jason Miller

HTML

<script src="https://npmcdn.com/preact"></script>
<script src="https://npmcdn.com/preact-portal"></script>

CSS

html, body {
	font: 14px/1.5 'Helvetica Neue', helvetica, arial, sans-serif;
	background: #F4F5F9;
}

h-box {
	display: flex;
}
h-box > * {
	flex: 1;
}

section {
	position: relative;
	border: 1px solid #CCC;
	background: #EEE;
	margin: 5px;
	padding: 5px;
}

.menu {
	position: absolute;
	left: 5px;
	top: 5px;
	display: inline-block;
	margin: 5px;
	padding: 0;
	background: #FFF;
	border-radius: 3px;
	box-shadow: 0 1px 5px rgba(0,0,0,0.3);
	z-index: 100;
	transform-origin: 0 0;
	animation: in forwards 250ms ease;
}
@keyframes in {
	from { opacity:0; transform:scale(0.01); }
}
.menu li {
	padding: 5px 40px 5px 20px;
	color: #555;
	list-style: none;
}
.menu li:hover {
	background: #DDD;
}

Babel + JSX

const { h, Component, render } = preact;
const Portal = preactPortal;
/** @jsx h */


// we want to render this component elsewhere in the DOM
class Menu extends Component {
	render({ left, top, items }) {
		return (
			<ul class="menu" style={{left, top}}>
				{ items.map( item => (
					<li>{ item }</li>
				)) }
			</ul>
		);
	}
}


class Toolbar extends Component {
	state = {
		open: false,
		into: 'body',
		items: ['Item 1', 'Item 2', 'Item 3']
	};

	toggle = () => {
		this.setState({ open: !this.state.open, left:null, top:null });
	};
	
	openPinned = e => {
		this.setState({
			open: true, into:'body',
			left:e.pageX, top:e.pageY
		});
	};
	
	moveInto(into) {
		return () => this.setState({ open:true, into, left:null, top:null });
	}

	render({ }, { items, open, into, left, top }) {
		return (
			<div>
				<button onClick={this.toggle}>Toggle Menu</button>
				<button onClick={this.openPinned}>Open Pinned</button>
				<div>
					Into:
					<button onClick={this.moveInto('body')}>{'into="body"'}</button>
					<button onClick={this.moveInto('#a')}>{'into="#a"'}</button>
					<button onClick={this.moveInto('#b')}>{'into="#b"'}</button>
				</div>
				{ open ? (
					<Portal into={into}>
						<Menu {...{left,top,items}} />
					</Portal>
				) : null }
			</div>
		);
	}
}


class App extends Component {
	render() {
		return (
			<div>
				<h1>{'preact-portal Example'}</h1>
				
				<p>The &lt;Portal&gt; component lets you render an arbitrary part of your JSX tree elsewhere in the DOM, without giving up diffing, mounting, etc</p>
				
				<Toolbar />

				<h-box>
					<section id="a">
						<h2>A</h2>
					</section>
					<section id="b">
						<h2>B</h2>
					</section>
				</h-box>

				<h2>How it works:</h2>
				<pre>{
`<div>
  <p>I am rendered normally, wherever mounted.</p>
  <Portal into="body">
    <p>I am rendered into document.body</p>
  </Portal>
</div>`
				}</pre>
			</div>
		);
	}
}

render(<App />, document.body);