Creating an Embeddable Medium Posts Widget Using React — Part 1.

Github Page Articles Section With Embedly

Introduction

I was recently working on revamping my user Github page and I was looking for a way to embed some of my web development and JavaScript projects medium posts on it. I initially settled for creating an embedly embed using their website and including the generated code in my html file but I didn’t really like how it turned out especially as it came with watermarks since it was a free embed (see image above).

Getting medium user data

As at the time of writing this, Medium’s API is write only and therefore has no provision for reading user data so it was not going to be of any use to me. They however have an RSS feed for each user and publication which looks like this: https://medium.com/feed/@{your_username}. This feed contains the posts along with their categories i.e those tags medium asks you for at the when you want to publish. These categories are what I am going to use to filter the list of posts I am eventually going to get.

Setting up my project

I created a react app using create-react-app and named it medium-posts-widget. In my src folder, I renamed my App.js, App.css to Widget.js and Widget.css respectively, removed the service-worker.jsandlogo.svg files, then in my index.js file, I changed the import and render calls to reflect my new files. Also in my public/index.html folder, I linked the relevant CDNs I would be using like the Bootstrap Style Sheet, Google’s PlayFair font, JQuery and Bootstrap.js. Next, I removed the styles in Widget.css since I was going to replace them with mine. Lastly, I removed the header and .app elements from my Widget.js file and replaced them with a single div element.

Creating my Widget Component

Recall that the App.js now called Widget.js component was a function component. I wanted to be able to store states of the widget so I changed it to a class component and added the following code.

import React, {Component} from 'react';
import './Widget.css';
class Widget extends Component {
constructor(props) {
super(props)
this.state = {
requestFailed: false,
active: 0
}
}
render (){
return(
<div className="container">
</div>
)
}
}
export default Widget;
  1. active which is used for the carousel items. It is set to 0 so that it matches the index of the first carousel item created later.

Rendering the Widget Component.

The widget component will now be rendered in index.js like this:

import React from 'react';
import ReactDOM from 'react-dom';
import Widget from './Widget';
const el = document.getElementById('medium-posts-widget');
const rssFeedLink = el.getAttribute('data-medium-rss')
ReactDOM.render(<Widget mediumRSSFeedLink={rssFeedLink} />, el);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="A Website Created for A Widget for Embedding Medium Posts"
/>
<link rel="apple-touch-icon" href="logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css?family=Playfair+Display:400,900&amp;display=swap" rel="stylesheet">
<title>Medium Posts Widget</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="medium-posts-widget" data-medium-rss="https://medium.com/feed/@adamichelllle"></div>
<script src="https://code.jquery.com/jquery-3.4.1.js" integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU=" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>
</html>

Fetching Data from User Medium Feed

Before doing this I had to declare some variable and functions at the top of my file just after the import statements.

const urlForFeedToJson = feed => `https://api.rss2json.com/v1/api.json?rss_url=${feed}`;
const keyCategories = ['javascript', 'front-end-development', 'responsive-web-design'];
const contains = (keyCategories, categories) => {
const [category1, category2, category3] = keyCategories
return categories.indexOf(category1) > -1 || categories.indexOf(category2) > -1 || categories.indexOf(category3) > -1
}
componentDidMount() {
fetch(urlForFeedToJson(this.props.mediumRSSFeedLink))
.then(response => {
if (!response.ok) {
throw Error("Network request failed")
}
return response
})
.then(data => data.json())
.then(data => {
const dataItems = data.items
const mediumPosts = dataItems.filter(item => item.categories.length > 0)
this.setState({
mediumPosts: mediumPosts
})
}, () => {
this.setState({
requestFailed: true
})
})
}

Filtering Feed JSON by Specific Array of Categories

Since I wanted to just return posts I have written about front-end-development, JavaScript and responsive-web-design, I had to use the contains function in the code above. Where I had const mediumPosts = dataItems.filter(item => item.categories.length > 0), I added && contains(keyCategories, item.categories), such that the line would became:

const mediumPosts = dataItems.filter(item => item.categories.length > 0 && contains(keyCategories, item.categories))

Adding the carousel for the slides

I wanted my widget to make use of cards like this Bootstrap 4 carousel with 3 card items, I once customize for my personal use case which I originally found here made by Anli from AzMind (thank you 😃). Therefore I had to add that bootstrap carousel component. I added the code found below within the div having the class container. The .caousel-inner div will contain the carousel items which I created after creating the card component.

        <div id="postsCardCarousels" className="carousel slide" data-ride="carousel" data-interval="false">
<div className="carousel-inner row w-100 mx-auto" role="listbox">

</div>
<a className="carousel-control-prev" href="#postsCardCarousels" role="button" data-slide="prev">
<span className="carousel-control-prev-icon" aria-hidden="true"></span>
<span className="sr-only">Previous</span>
</a>
<a className="carousel-control-next" href="#postsCardCarousels" role="button" data-slide="next">
<span className="carousel-control-next-icon" aria-hidden="true"></span>
<span className="sr-only">Next</span>
</a>
</div>

Creating the Card Component

I created a Card.js component in a components folder which was going to take in a post props. This component was going to create a bootstrap card with each post details. I wanted to pick only the post-title, post-content, link to the post and finally the thumbnail. The post content however is too long to be fitted into a card and I wanted to truncate it and create like a preview of the post. From the article above I was able to get a function to convert the post-content to text and shorten the text. So at the end of the day my card component looked like this:

import React from 'react';const tagToText = (node) => {
let tag = document.createElement('div')
tag.innerHTML = node
node = tag.innerText
return node
}
const shortenText = (text,startingPoint ,maxLength) => {
return text.length > maxLength?
text.slice(startingPoint, maxLength):
text
}
function Card(props) {
return(
<div className="card">
<img src={props.post.thumbnail} className="card-img-top post-thumbnail" alt={props.post.title}></img>
<div className="card-body">
<h5 className="card-title post-title">{props.post.title}</h5>
<p className="card-text post-preview">{'...' + shortenText(tagToText(props.post.content), 60, 200) + '...'}</p>
<a href={props.post.link} className="btn btn-link-grey">Read this article on Medium.com</a>
</div>
</div>
)
}
export default Card

Importing and Using the Card Component to create Carousel Items

At the top of my Widget.js file after the last import statement, I added another import for my card component.

import Card from './components/Card';
    if (this.state.requestFailed) return <div className="container"><p className="ml-4">Oops! Sorry we couldn't load your medium articles</p></div>
if (!this.state.mediumPosts) return <div className="container"><p className="ml-4">Loading articles from my medium feed ...</p></div>

const mediumPosts = this.state.mediumPosts
const posts = mediumPosts.length > 6 ? mediumPosts.slice(7) : mediumPosts;

const cardCarousels = posts.map((post, index) =>
<div className={this.state.active === index ? 'carousel-item col-12 col-sm-12 col-md-6 col-lg-4 active' : 'carousel-item col-12 col-sm-12 col-md-6 col-lg-4'} key={index}>
<Card post={post} />
</div>
)
Picture of what the widget looks like. — I only have one post in the categories I chose which is why there’s only one post.

Libraries and Tools

  1. Create React App
  2. RSS to JSON Online Converter.

Resources

I’m really grateful for all these resources listed below that helped me in doing this.

  1. How to create an embeddable Vue Widget with Custom Vue Component by DanaJanoskova.
  2. How to Display Medium Posts on a Website with Plain Vanilla JS Basic API Usag by Konrad Dariusz Wołoszyn.
  3. Bootstrap Carousel Multiple Items by Anil.
  4. Display Medium articles on your site.

Front End Dev || Writer || Blogger

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store