Create a Scrolling Ticker using the Hacker News RSS Feed in React
Stock-Exchange Style Ticker Using ReactJS
This article will walk through how to create a scrolling ticker (using a neat third-party package) to serve RSS content from the Hacker News API. The end result will look similar to the screenshot below.
To see the ticker in live action, you can check it out here!
The text that you can see coming through the ticker, are posts from Hacker News, using the hnrss.org API.
What is an RSS Feed?
RSS stands for ‘Really Simple Syndication’. In short, RSS is a subscription model that allows applications to access and receive updates on media content. In our case, the RSS Feed we are using is hnrss.org.
React Ticker Package
I’m a huge fan of not reinventing the wheel! And since there is an open source NPM package out there with ‘scrolling ticker’ functionality ready-to-go, why not use it?
First, add this package to your package.json file;
npm install react-ticker
OR
yarn add react-ticker
You can visit the documentation here https://www.npmjs.com/package/react-ticker.
Setting up the UI and Creating the Ticker Component
First, create a TickerFeed.jsx component. Name it whatever you like, just make sure that it has a different name to the package import we will add in later.
Now, create a basic export function in TickerFeed.jsx and import the react-ticker package from earlier as Ticker;
// TickerFeed.jsx
import React, { useState, useEffect } from 'React';
import Ticker from 'react-ticker';
const TickerFeed = () => {
return (
<div></div>
);
};
export default TickerFeed;
Now, in the App.jsx file (or whatever file will display the ticker, maybe you have a Dashboard.jsx or something of that nature), import TickerFeed.jsx;
// App.jsx
import React from 'react';
import TickerFeed from './TickerFeed';
const App = () => {
return (
<div className="app">
<TickerFeed />
</div>
);
};
Our ticker component will take no props, so to render the ticker just add it in as <TickerFeed />.
Depending on what styles you wrap the component in will depend on how its located and positioned on the screen, which is up to you. I’m going to discuss the styles required to make the scrolling ticker look awesome!
Back in TickerFeed.jsx, we need to create a function that will get the RSS data and render the results.
// TickerFeed.jsx
import React, { useState, useEffect } from 'React';
import Ticker from 'react-ticker';
const TickerFeed = () => {
const GetRssFeedData = () => {
const [feed, setFeed] = useState();
useEffect(() => {
}, []);
};
return (
<div></div>
);
};
export default TickerFeed;
The GetRssFeedData component will run useEffect on each re-render since there is no value in the useEffect dependency array.
Now we can add the react-ticker element;
// TickerFeed.jsx
import React, { useState, useEffect } from 'React';
import Ticker from 'react-ticker';
const TickerFeed = () => {
const GetRssFeedData = () => {
const [feed, setFeed] = useState();
useEffect(() => {
}, []);
};
return (
<div className="ticker">
<div className="ticker__field">
<Ticker offset="run-in">
{() =>
<GetRssFeedData />
}
</Ticker>
</div>
</div>
);
};
export default TickerFeed;
I’ve wrapped the Ticker element in a div with the class name ticker__field and then wrapped that in another div with the class name ticker.
The prop, offset, in the Ticker component determines the alignment of the first element that scrolls along. In the case above, I’ve set it to ‘run-in’ which will hide the first element, so that the ticker itself starts empty and then populates. Visit the NPM documentation page for a full list of props that the component will accept, and the different values they can be set to.
The Main Styles
Before moving on the the GetRssFeedData functionality, we can add styles to the first two divs.
.ticker {
position: absolute;
width: 100%;
height: 40px;
z-index: 1;
}
.ticker__field {
position: fixed;
top: 50px;
left: 0;
width: 100%;
height: $element-height;
background-color: rgb(35, 35, 35);
opacity: 0.85;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: subpixel-antialiased;
}
The useEffect Functionality
The useEffect hook will run every time the component has to re-render, therefore, we want to place the API call inside of the useEffect.
// TickerFeed.jsx
import React, { useState, useEffect } from 'React';
import Ticker from 'react-ticker';
const TickerFeed = () => {
const GetRssFeedData = () => {
const [feed, setFeed] = useState();
useEffect(() => {
let mounted = true;
const getRss = async () => {
await fetch('https://hnrss.org/newest')
.then((res) => res.text())
.then((data) => {
const xmlDoc =
new DOMParser()
.parseFromString(data, 'text/xml');
const items =
Array.from(
xmlDoc.querySelectorAll('item'))
.map((item) => ({
title: item
.querySelector('title').textContent,
link: item
.querySelector('link').textContent,
}));
if (mounted) {
setFeed(items);
}
});
};
getRss();
return () => {
mounted = false;
};
}, []);
};
return (
<div className="ticker">
<div className="ticker__field">
<Ticker offset="run-in">
{() =>
<GetRssFeedData />
}
</Ticker>
</div>
</div>
);
};
export default TickerFeed;
You will see that first, I created a function within the useEffect called getRss that is invoked by calling it towards the end of the useEffect hook.
The first line under the useEffect declaration sets a boolean value mounted to true. At the end of the useEffect I’ve place a return statement that sets mounted to false. This is required as the useEffect functionality takes on asynchronous behaviour by making fetch requests, and ensures that any promises are cancelled before the component is mounted to the DOM. This will avoid memory leaks within your application, which if not taken care of, your browser will complain about.
Then, we use fetch to call the endpoint ‘https://hnrss.org/newest’. Alternatively this can and should be done by calling your own server that will then request data from the API, that way effective caching and cross-site strategies can be implemented.
After the ‘hnrss’ data is fetched, we create a new DOMParser object allowing us to parse the XML response data. From there we map through the xmlDoc variable (that we set the XML response to) and extract ‘title’ and ‘link’ to display in the scrolling ticker.
Finally, if and only if the component is still mounted to the DOM, we set the feed state with the new items array.
GetRssFeedData Return Value
Now that we have fetched and set the data we need for the scrolling ticker, we need to set a return value for the GetRssFeedData component.
// TickerFeed.jsx
import React, { useState, useEffect } from 'React';
import Ticker from 'react-ticker';
const TickerFeed = () => {
const GetRssFeedData = () => {
const [feed, setFeed] = useState();
useEffect(() => {
let mounted = true;
const getRss = async () => {
await fetch('https://hnrss.org/newest')
.then((res) => res.text())
.then((data) => {
const xmlDoc =
new DOMParser()
.parseFromString(data, 'text/xml');
const items =
Array.from(
xmlDoc.querySelectorAll('item'))
.map((item) => ({
title: item
.querySelector('title').textContent,
link: item
.querySelector('link').textContent,
}));
if (mounted) {
setFeed(items);
}
});
};
getRss();
return () => {
mounted = false;
};
}, []);
return feed ? (
<p className="ticker__field__text">
{feed.map((items) => (
<a
key={items.title}
id={uuidv4()}
href={items.link || ''}
target="_blank" rel="noopener">
{items.title}
</a>
))}
</p>
) : (
<p className="ticker__field__text">
<span style={{ color: '#f4f4f4' }}>
Just Waiting for the Feed!
</span>
</p>
);
};
return (
<div className="ticker">
<div className="ticker__field">
<Ticker offset="run-in">
{() =>
<GetRssFeedData />
}
</Ticker>
</div>
</div>
);
};
export default TickerFeed;
The return value is a conditional, where if the feed state is set, then that is returned, otherwise placeholder text ‘Just Waiting for the Feed!’ is returned instead (if there was an error fetching the API data).
The contents of the returned <p> element is set to items.title which is then wrapped in a link value of items.link. This will link to the corresponding Hacker News post.
Final Styling
In the return value, the <p> element has a class name of ‘ticker__field__text’;
.ticker__field__text {
line-height: 50px;
font-size: 14px;
vertical-align: middle;
white-space: nowrap;
margin-left: 3rem;
padding-top: 0px;
height: 40px;
}
a {
text-decoration: none;
color: #f4f4f4;
padding-right: 3rem;
}
Putting it Altogether
The final code for TickerFeed.jsx should look like the following;
import React, { useState, useEffect } from 'react'; | |
import { v4 as uuidv4 } from 'uuid'; | |
import Ticker from 'react-ticker'; | |
// styles | |
import './css/tickerFeedscss'; | |
const TickerFeed = () => { | |
const GetRssFeedData = () => { | |
const [feed, setFeed] = useState(); | |
useEffect(() => { | |
let mounted = true; | |
const getRss = async () => { | |
await fetch'https://hnrss.org/newest') | |
.then((res) => res.text()) | |
.then((data) => { | |
const xmlDoc = new DOMParser().parseFromString(data, 'text/xml'); | |
const items = Array.from(xmlDoc.querySelectorAll('item')).map((item) => ({ | |
title: item.querySelector('title').textContent, | |
link: item.querySelector('link').textContent, | |
})); | |
if (mounted) { | |
setFeed(items); | |
} | |
}); | |
}; | |
getRss(); | |
return () => { | |
mounted = false; | |
}; | |
}, []); | |
return feed ? ( | |
<p className="ticker__field__text"> | |
{feed.map((items) => ( | |
<a key={items.title} id={uuidv4()} href={items.link || ''} target="_blank" rel="noopener"> | |
{items.title} | |
</a> | |
))} | |
</p> | |
) : ( | |
<p className="ticker__field__text"> | |
<span style={{ color: '#f4f4f4' }}> | |
Just Waiting for the HackerNews Feed! | |
</span> | |
</p> | |
); | |
}; | |
return ( | |
<div className="ticker"> | |
<div className="ticker__field"> | |
<Ticker offset="run-in">{() => <GetRssFeedData />}</Ticker> | |
</div> | |
</div> | |
); | |
}; | |
export default TickerFeed; |
I hope you found some of the ideas in this article either helpful or at least interesting! Feel free take a look at some of the other stuff I’ve written on full-stack development and devops. 😃