How to add a React component that rotates recommended items on your website

As I had mentioned in how to monetise a programmer's website effectively, affiliate marketing can be an effective way for your website to earn money.

Since affiliate marketing programs revolves around links, we can build a catalogue of items that we wish to recommend to visitors to our websites.

If you have already built that catalogue of product recommendations and an API endpoint, you can build a sticky component in your sidebar and rotates recommended items from there.

Since I have been hearing good feedback about React, I implemented such a feature on my website with React.

In case you are looking to do the same, this is how you can add a React component that rotates recommended items on your website.

Prerequisites

Previously, I had already built a Python 3 Flask endpoint that returns some recent records from my recommended items catalogue.

Whenever I send a HTTP request to https://www.techcoil.com/api/v1/shop/items/, I will get a JSON as the HTTP response:

{
  "items": [
    {
      "image_url": "/shop/img/Raspberry-Pi-4-4GB-Basic-Starter-Kit-with-Fan-(4GB-RAM)-500x500.jpg",
      "referral_url": "https://www.amazon.com/gp/product/B07VYC6S56/?ie=UTF8&tag=clivsperswebs-20",
      "excerpt": "<p>\r\nWith a CanaKit Raspberry Pi 4 4GB Basic Starter Kit with Fan (4GB RAM), you are giving a powerful computer which takes up very little space.\r\n</p>\r\n<p>\r\nWhoever receives this as a gift will like you even more.\r\n</p>\r\n<p>\r\nPair it with a <a href=\"https://www.amazon.com/SanDisk-128GB-Extreme-microSD-Adapter/dp/B07FCMKK5X/ref=as_li_ss_tl?ie=UTF8&linkCode=ll1&tag=clivsperswebs-20&linkId=c3c367c2d46c69f7e8df6a3dd5190dbc&language=en_US\" target=\"_blank\">microSD card</a> and make your favourite geek happy.\r\n</p>\r\n",
      "item_url": "/shop/Raspberry-Pi-4-4GB-Basic-Starter-Kit-with-Fan-(4GB-RAM)",
      "title": "CanaKit Raspberry Pi 4 4GB Basic Starter Kit with Fan (4GB RAM)"
    },
    {
      "image_url": "/shop/img/CINEMOOD-Portable-Movie-Theater.jpg",
      "referral_url": "https://www.amazon.com/gp/product/B079HJB9P8/?ie=UTF8&tag=clivsperswebs-20",
      "excerpt": "<p>\r\nWhat can you do to keep your kids entertained on a long flight?\r\n</p>\r\n<p>\r\nWith CINEMOOD Portable Movie Theater, you can easily create a movie theater as long as you have a wall to project the screen.\r\n</p>\r\n<p>\r\nIn addition to the physical hardware, it comes with a library of 40 Disney e-books and 25 safety videos and stories.\r\n</p>",
      "item_url": "/shop/CINEMOOD-Portable-Movie-Theater",
      "title": "CINEMOOD Portable Movie Theater"
    },
    {
      "image_url": "/shop/img/UGREEN-USB-3.0-Sharing-Switch-Selector-with-4-USB-Port-for-2-Computers.jpg",
      "referral_url": "https://www.amazon.com/gp/product/B01N6GD9JO/?ie=UTF8&tag=clivsperswebs-20",
      "excerpt": "<p>\r\nNeed to easily switch ONE set of USB peripherals between two computers?\r\n</p>\r\n<p>\r\nHave a look at UGREEN USB 3.0 Sharing Switch Selector with 4 USB Port for 2 Computers.\r\n</p>\r\n<p>\r\nWith this little device, you no longer have to plug and unplug USB devices while working with two different computers.\r\n</p>",
      "item_url": "/shop/UGREEN-USB-3.0-Sharing-Switch-Selector-with-4-USB-Port-for-2-Computers",
      "title": "UGREEN USB 3.0 Sharing Switch Selector with 4 USB Port for 2 Computers"
    },
    {
      "image_url": "/shop/img/LEGO21312-Ideas-Women-of-NASA.jpg",
      "referral_url": "https://www.amazon.com/gp/product/B071W77MBJ/?ie=UTF8&tag=clivsperswebs-20",
      "excerpt": "<p>Let the women of NASA inspire your girl to choose a technology career.</p>\r\n<p>Featuring Nancy Grace Roman, Margaret Hamilton, Sally Ride and Mae Jemison alongside Lego builds that represent their areas of expertise.</p>\r\n<p>So get this Lego set for your techie girl.</p>",
      "item_url": "/shop/LEGO21312-Ideas-Women-of-NASA",
      "title": "LEGO 21312 - Ideas Women of NASA"
    },
    {
      "image_url": "/shop/img/Dell-U3417W-FR3PK-34-Inch-Screen-Led-Lit-Monitor.jpg",
      "referral_url": "https://www.amazon.com/gp/product/B01IOO4TIM/?ie=UTF8&tag=clivsperswebs-20",
      "excerpt": "<p>A computer monitor is one item to increase your productivity for computing tasks.</p>\r\n<p>For this purpose, a Dell U3417W FR3PK 34-Inch Screen Led-Lit Monitor fits the bill really well.</p>\r\n<p>Indeed, its curvature along with the 34 inch screen size enables you to see many windows in one screen.</p>\r\n",
      "item_url": "/shop/Dell-U3417W-FR3PK-34-Inch-Screen-Led-Lit-Monitor",
      "title": "Dell U3417W FR3PK 34-Inch Screen Led-Lit Monitor"
    },
    {
      "image_url": "/shop/img/Nexstand-Laptop-Stand.jpg",
      "referral_url": "https://www.amazon.com/gp/product/B01HHYQBB8/?ie=UTF8&tag=clivsperswebs-20",
      "excerpt": "<p>A laptop gives you the mobility to do real computing work. However, there may be times when you wish that your laptop screen can be elevated at a more comfortable viewing angle. </p>\r\n<p>Bring along a Nexstand Laptop Stand. This laptop stand is foldable and can be transformed into a small form factor that weighs just 8 ounces.</p>",
      "item_url": "/shop/Nexstand-Laptop-Stand",
      "title": "Nexstand Laptop Stand"
    },
    {
      "image_url": "/shop/img/Logitech-MX-Master-2S-Wireless-Mouse.jpg",
      "referral_url": "https://www.amazon.com/gp/product/B071YZJ1G1/?ie=UTF8&tag=clivsperswebs-20",
      "excerpt": "<p>If you are a right-hander looking for a good quality mouse that can help you be productive in your work, get the Logitech MX Master 2S Wireless Mouse.</p>\r\n<p>The Logitech MX Master 2S Wireless Mouse does not require changing of batteries and supports both horizontal and vertical scrolling.</p>",
      "item_url": "/shop/Logitech-MX-Master-2S-Wireless-Mouse",
      "title": "Logitech MX Master 2S Wireless Mouse"
    },
    {
      "image_url": "/shop/img/RAVPower-Solar-Charger-24W-Solar-Panel-with-3-USB-Ports.jpg",
      "referral_url": "https://www.amazon.com/gp/product/B06XBGSS2R/?ie=UTF8&tag=clivsperswebs-20",
      "excerpt": "<p>Are you an outdoor person who often bathe in strong sunlight?</p>\r\n<p>If that's the case, you will want to get the RAVPower Solar Charger.</p>\r\n<p>With this awesome piece of device, you can charge your hungry devices when you are out in the sun.</p>\r\n<p>Pretty handy for beach goers and hikers.</p>",
      "item_url": "/shop/RAVPower-Solar-Charger-24W-Solar-Panel-with-3-USB-Ports",
      "title": "Solar Charger RAVPower 24W Solar Panel with 3 USB Ports"
    },
    {
      "image_url": "/shop/img/Eco-Bamboo-Multi-Device-Charging-Station-Dock-and-Organizer.jpg",
      "referral_url": "https://www.amazon.com/gp/product/B009CFMO0S/?ie=UTF8&tag=clivsperswebs-20",
      "excerpt": "<p>Mark your mobile device charging point with a Eco Bamboo Multi-Device Charging Station Dock & Organizer.</p>\r\n<p>This tough and elegant station dock & organizer will keep your charging cables from becoming an eyesore.</p>\r\n<p>Buy one for yourself and your loved ones!</p>",
      "item_url": "/shop/Eco-Bamboo-Multi-Device-Charging-Station-Dock-and-Organizer",
      "title": "Eco Bamboo Multi-Device Charging Station Dock & Organizer"
    },
    {
      "image_url": "/shop/img/KOPACK-Slim-Business-Laptop-Backpack.jpg",
      "referral_url": "https://www.amazon.com/gp/product/B01FFA730Y/?ie=UTF8&tag=clivsperswebs-20",
      "excerpt": "<p>Need a tear-resistant and water resistant backpack to carry your laptop around? Have a look at KOPACK's Slim Business Laptop Backpack. It is a feature-packed backpack that doesn't cost you too much. The laptop compartment is designed to foil a pickpocket's attempt to remove your laptop without your knowing.</p>\r\n<p>Check out the full feature list at Amazon.</p>\r\n",
      "item_url": "/shop/KOPACK-Slim-Business-Laptop-Backpack",
      "title": "KOPACK Slim Business Laptop Backpack"
    }
  ],
  "total": 61,
  "pages": 7,
  "per_page": 10,
  "page": 1
}

Given that, I will be able to retrieve the list of items when my React component is mounted.

In order for you to follow through this tutorial in its entirety, you should have a similar endpoint where you can fetch some records from.

Even if you do not have the HTTP endpoint, you can easily tweak the codes to rotate recommended items that you hardcode beforehand.

Coding the React component that rotates recommended products

First, let's look at how we can code the React component that rotates recommended items at a periodic interval.

Before writing the first line of codes, let's define some expectations for our React component.

  1. When it first get mounted in the HTML document, the React component will fetch the list of recommended items to rotate periodically.
  2. The React component will display one item at a time. Each item will have a title and an image that link to a webpage.
  3. After fetching the list of recommended products, the React component will start a timer process to change the current display item.
  4. When the React component is destroyed, it should also stop the timer process.

The JSX codes for the React component

Given these points, the following is the React component coded in JSX:

class Recommendations extends React.Component {
  constructor(props) {
    super(props);
    this.state = {"items": [
    {
      "image_url": "/ph/img/prompt-for-gift-ideas.jpg",
      "referral_url": "/shop/",
      "title": "Get some gift ideas here"
    }
    ],
    "currentDisplayIndex": 0};
  }

  rotateDisplayIndex() {
    let nextDisplayIndex = (this.state.currentDisplayIndex + 1) % this.state.items.length;
    this.setState({currentDisplayIndex: nextDisplayIndex});
  }

  startTimer() {
    this.interval = setInterval(() => this.rotateDisplayIndex(), 10000);
  }

  componentDidMount() {
    fetch('/api/v1/shop/items/?per_page=5&page=1')
      .then(response => response.json())
      .then(data => this.setState({ items: data.items }))
      .then(() => this.startTimer());
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  render() {
    let currentItem = this.state.items[this.state.currentDisplayIndex];
    let referralUrl = currentItem['referral_url'];
    let title = currentItem['title'];
    let imageUrl = currentItem['image_url'];
    return (
      <div class="item">
        <a href={referralUrl} target="_blank">
          <img src={imageUrl} alt={title}/>
        </a>
        <h4>
          <a href={referralUrl} target="_blank">
            {title}
          </a>
        </h4>
      </div>
    );
  }
}

const domContainer = document.querySelector('#recommended-items-container');
if (domContainer) {
  ReactDOM.render(<Recommendations/>, domContainer);
}

Defining the Recommendations class

As shown above, we first define a class that extends from React.Component.

Within the class, we define a constructor that sets the initial state for the React component.

Once we have done so, we define the rotateDisplayIndex function that set currentDisplayIndex to the index of the next display item. Since the modulus operator will reset to 0 when currentDisplayIndex equals the length of the list, we get the effect of displaying the first item after we had displayed the last.

After that, we define the startTimer function which will create a timer that calls rotateDisplayIndex every 10 seconds. In order to be able to delete the timer at a later point in time, we saved a reference of that timer to this.interval.

When we have the function to start the timer, we then define the componentDidMount function. Since this function is called when React first mounts the component, we use the fetch function to retrieve items for our display. Once fetch is able to get the data, it keeps the list of items in the component's state. After that, it starts the timer to rotate the recommended items using the startTimer function.

Following the componentDidMount function, we define the componentWillUnmount function. Since React will call this function before the component is unmounted, we get to clear the timer that we have created earlier.

Lastly, we define the render function which React will use to build the component within the HTML DOM. Within this function, we use currentDisplayIndex to get the item to display.

Rendering an instance of the Recommendations class with ReactDom

After we had defined the Recommendations class, we first use document.querySelector to look for an HTML element to inject our React component.

If we can find an element with recommended-items-container as its ID, we use ReactDOM.render to inject an instance of our React component into that element.

Creating the HTML document that will hold the React component

After we have created the React component that can rotate recommended products on our website, we can then build the HTML document that will hold that React component.

Rendering the JSX codes directly into the HTML document

The quickest way to render the React component is to reference our JSX codes directly in the HTML document. Assuming that we save the JSX codes as recommendations.jsx in the same folder as our HTML document, the following HTML codes renders the React component that we had discussed earlier.

<html>
<head>
  <title>Rotate product recommendations with React</title>
  <meta charset="UTF-8" />
  <style>
    #recommended-items-container {
      background-color: #EDF8FF;
      overflow: auto;
      width: 250px;
    }
    #recommended-items-container .item {
      display: block;
      text-align: center;
      margin: 1rem;
    }
    #recommended-items-container .item img {
      max-width: 100%;
    }
  </style>
</head>
<body>
<div id="recommended-items-container"></div>
<script crossOrigin src="https://unpkg.com/react@16/umd/react.production.min.js" ></script>
<script crossOrigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" ></script>
<script crossOrigin src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel" src="recommendations.jsx"></script>
</body>
</html>

As shown above, in the body of the HTML document, we define a div element with recommended-items-container as the ID. After that, we included three external scripts before including the .jsx file that hold the JSX codes that we had discussed earlier.

In case you are wondering, the first 2 external links are CDN links for React and ReactDOM while the 3rd link is the CDN link for the babel standalone compiler.

In addition to that, we mark the script to our JSX code as type="text/babel" to get the babel standalone compiler to compile it on the fly.

Precompiling the JSX codes before including it in the HTML document

When you access the previous HTML document from your web server, you will get the following warning message:

babel.min.js:1 You are using the in-browser Babel transformer. Be sure to precompile your scripts for production - https://babeljs.io/docs/setup/
(anonymous) @ babel.min.js:1

If you choose to take this advice, then you may want to compile recommendations.jsx into browser-compatible JavaScript before including them in your HTML document.

For example, you can use Babel REPL to convert your JSX codes into browser-compatible JavaScript:
Babel REPL converting your JSX codes into browser-compatible JavaScript

When we provide the JSX codes that we had discussed earlier to Babel REPL, we yield the following browser-compatible JavaScript codes:

"use strict";

function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }

function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

var Recommendations =
/*#__PURE__*/
function (_React$Component) {
  _inherits(Recommendations, _React$Component);

  function Recommendations(props) {
    var _this;

    _classCallCheck(this, Recommendations);

    _this = _possibleConstructorReturn(this, _getPrototypeOf(Recommendations).call(this, props));
    _this.state = {
      "items": [{
        "image_url": "/ph/img/prompt-for-gift-ideas.jpg",
        "referral_url": "/shop/",
        "title": "Get some gift ideas here"
      }],
      "currentDisplayIndex": 0
    };
    return _this;
  }

  _createClass(Recommendations, [{
    key: "rotateDisplayIndex",
    value: function rotateDisplayIndex() {
      var nextDisplayIndex = (this.state.currentDisplayIndex + 1) % this.state.items.length;
      this.setState({
        currentDisplayIndex: nextDisplayIndex
      });
    }
  }, {
    key: "startTimer",
    value: function startTimer() {
      var _this2 = this;

      this.interval = setInterval(function () {
        return _this2.rotateDisplayIndex();
      }, 10000);
    }
  }, {
    key: "componentDidMount",
    value: function componentDidMount() {
      var _this3 = this;

      fetch('/api/v1/shop/items/?per_page=5&page=1').then(function (response) {
        return response.json();
      }).then(function (data) {
        return _this3.setState({
          items: data.items
        });
      }).then(function () {
        return _this3.startTimer();
      });
    }
  }, {
    key: "componentWillUnmount",
    value: function componentWillUnmount() {
      clearInterval(this.interval);
    }
  }, {
    key: "render",
    value: function render() {
      var currentItem = this.state.items[this.state.currentDisplayIndex];
      var referralUrl = currentItem['referral_url'];
      var title = currentItem['title'];
      var imageUrl = currentItem['image_url'];
      return React.createElement("div", {
        class: "item"
      }, React.createElement("a", {
        href: referralUrl,
        target: "_blank"
      }, React.createElement("img", {
        src: imageUrl,
        alt: title
      })), React.createElement("h4", null, React.createElement("a", {
        href: referralUrl,
        target: "_blank"
      }, title)));
    }
  }]);

  return Recommendations;
}(React.Component);

var domContainer = document.querySelector('#recommended-items-container');

if (domContainer) {
  ReactDOM.render(React.createElement(Recommendations, null), domContainer);
}

Once you have gotten the browser-compatible version of your React component codes, you can then save it in a regular JavaScript file and include it in your HTML document.

For example, if you save the compiled codes as recommendations-compiled.js, you can then render your React component with the following HTML document:

<html>
<head>
  <title>Rotate product recommendations with React</title>
  <meta charset="UTF-8" />
  <style>
    #recommended-items-container {
      background-color: #EDF8FF;
      overflow: auto;
      width: 250px;
    }
    #recommended-items-container .item {
      display: block;
      text-align: center;
      margin: 1rem;
    }
    #recommended-items-container .item img {
      max-width: 100%;
    }
  </style>
</head>
<body>
<div id="recommended-items-container"></div>
<script crossOrigin src="https://unpkg.com/react@16/umd/react.production.min.js" ></script>
<script crossOrigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" ></script>
<script src="recommendations-compiled.js"></script>
</body>
</html>

Since we had compiled the codes to render our React component, we can remove the CDN link to the babel standalone compiler. In addition to that, we can remove type="text/babel" from the script element that link to the compiled codes.

Ending conclusion

In summary, React provides us with a quick way to build HTML components into existing websites with minimal coding.

JSX provides a clean way for us to code these components while Babel helps to compile browser-compatible JavaScript that can run in browsers.

About Clivant

Clivant a.k.a Chai Heng enjoys composing software and building systems to serve people. He owns techcoil.com and hopes that whatever he had written and built so far had benefited people. All views expressed belongs to him and are not representative of the company that he works/worked for.