Skip to content

Instantly share code, notes, and snippets.

@zroger
Last active May 11, 2019 20:57
Show Gist options
  • Save zroger/a62ed69ddba7deca2b279ac4b71caa8d to your computer and use it in GitHub Desktop.
Save zroger/a62ed69ddba7deca2b279ac4b71caa8d to your computer and use it in GitHub Desktop.
Log tailing w/ React + IntersectionObserver
<div id="root"></div>
class IntersectionTarget extends React.Component {
handleIntersection = (entries) => {
const entry = entries.find(entry => entry.target == this.element)
if (entry) {
this.props.onChange(entry);
}
};
handleRef = (element) => {
this.element = element;
if (this.observer) {
this.observer.disconnect();
} else {
this.observer = new IntersectionObserver(
this.handleIntersection,
this.props.options,
);
}
if (element) {
this.observer.observe(element);
}
};
render() {
const {children, Component='div'} = this.props;
return <Component ref={this.handleRef}>{children}</Component>;
}
}
class App extends React.Component {
state = {
items: [],
tail: null,
};
handleIntersect = (entry) => {
this.setState({
tail: entry.isIntersecting ? entry.target : null,
});
};
loadData() {
fetch("https://litipsum.com/api/1/json")
.then(resp => {
return resp.json();
})
.then(data => {
this.setState(state => (
{items: state.items.concat(data)}
));
const seconds = Math.max(this.state.items.length, 3);
console.log("Fetching more data in %d seconds", seconds);
setTimeout(() => {this.loadData()}, seconds * 1000);
})
}
componentDidMount() {
this.loadData();
}
componentDidUpdate() {
const {tail} = this.state;
if (tail) {
tail.scrollIntoView(false);
}
}
render() {
return (
<React.Fragment>
<div id="overlay">
<p>
This demo adds a new item to the list every second to
simulate constant updates, for instance, from streaming logs.
If the <code>IntersectionTarget</code> is visible before an
update, then after the update, the window will be scrolled
to keep it visible.
</p>
<div>
Tailing: {this.state.tail ? "true" : "false"} |
Items: {this.state.items.length}
</div>
</div>
{this.state.items.map((item, i) => (
<blockquote key={i}>
<p>{item.text}</p>
<cite>— {item.title}</cite>
</blockquote>
))}
<IntersectionTarget onChange={this.handleIntersect}>
<p className="target">
<code>IntersectionTarget</code>
</p>
</IntersectionTarget>
</React.Fragment>
)
}
}
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
body {
scroll-behavior: smooth;
}
#root {
padding-top: 120px;
}
#overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
background: #eee;
border: solid 1px #ddd;
padding: 10px 20px;
}
.target {
font-size: 12px;
text-align: center;
}
blockquote {
border-bottom: solid 1px #ccc;
padding-bottom: 10px;
}
blockquote cite {
display: block;
text-align: right;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment