Creating React “Widgets” that can be embedded on any website, by anyone
Tags: react
Published: 2020-08-10 16:01:30

> A software widget is a relatively simple and easy-to-use software application or component made for one or more different software platforms.
[read more](https://en.wikipedia.org/wiki/Software_widget)

Some of examples of a widget are the [related content](https://content.fortune.com/wp-content/uploads/2014/08/business-insider-taboola-081814.png?resize=750,500) widget by Taboola. Or the [newsletter](https://downloads-pagekit.storage.googleapis.com/bixie/mailchimp/image.jpeg) widget, by MailChimp. In this article, we'll teach you how to build a [reddit widget](https://javascriptpros.com/wp-content/uploads/2020/08/full-reddit-widget.gif).


# Why would I do this?


One example is for versatility in a widget you're making. Either for a client or for the world. A widget should be embeddable in as many places as possible, regardlesss of the software. Whether that website is made using WebFlow, Wordpress, Shopify, Drupal, doesn't matter.  Additionally, its common for a widget to exist multiple times on the same page. Let's imagine a widget where we display the last 5 posts of a given subreddit. I should be able to embed that widget multiple times, for multiple subreddits, on the same page.

Keep in mind, we aren't building this widget for React developers. If that were the case, we'd just build a React Component and publish it on npm. Instead, we're building a widget that can be used by anyone, even a non-coder, outside of React. 

We'll go over exactly how to do this. We'll start off by teaching you how to initialize multiple versions of your React App on the same page. Then, we'll learn how to pass data down the DOM, into our React App. This will allow us to present each of those widgets in different ways, by setting some attributes. Attributes which your customers can easily configure, without knowing how to code. 

To get started, let's initialize a typical react app, using create-react-app.

```bash
npx create-react-app reddit-widget
```

# ReactDOM's Render Function

When you first initialize a React App using `create-react-app`, you'll notice React attaches itself to a single element. 

```javascript
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
```

[ReactDOM](https://reactjs.org/docs/react-dom.html)'s render function primarily takes two arguments. The first is the React Component you'll be injecting into the DOM. The second is the actual DOM element you'll be injecting the React Component into. 

In the case above, we're injecting our `<App />` component (wrapped in React's Strict Mode), into the `#root` div container in the DOM. Which you can find by navigating to `public/index.html`.

```html
<div id="root"></div>
```

## Multiple Instanes of React
[view final commit](https://github.com/GioLogist/article-react-reddit-widget/commit/793d725e058e9165400027adb4bf6ac4e6cd11ed)

Now, what happens if we want multiple instances of this React App? We know how ReactDOM's render function works. Instead of injecting our app into a single div in the DOM, let's inject it into multiple. 

First, we'll update *index.js* to iterate over multiple divs. To do this, we'll use  [document.querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) and search for all divs with a `reddit_widget` class specified. Then, we'll inject our React App into each of them.

```javascript
// Find all widget divs
const WidgetDivs = document.querySelectorAll('.reddit_widget')

// Inject our React App into each
WidgetDivs.forEach(Div => {
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
    Div
  );
})
```

At this point, our React App will be blank. That's because we don't have any divs with the *reddit_widget* class yet. Let's update our `public/index.html` file. 

```html
    <div class="reddit_widget"></div>
    <div class="reddit_widget"></div>
```

Great, now we have multiple versions of our React App running at the same time! This is pretty much the foundation for this article ⚡️ 

![Multiple-Instanes-of-React](https://javascriptpros.com/wp-content/uploads/2020/08/Multiple-Instanes-of-React.gif)

# Passing Data Attributes
[view final commit](https://github.com/GioLogist/article-react-reddit-widget/commit/886c6d6aaaa035b4316cbedb505e3b5f0440a2bb)

So we have our React App rendering multiple times in a page. This within itself isn't useful. We want each instance of our app to contain different data or functionality. 

There are tons of ways to pass data to and from a React App. In this article, we'll cover using [data attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*).

## Reading DOM attributes in a React component

In React, we use [Props](https://reactjs.org/docs/components-and-props.html) to attach useful data to our components. In HTML, we have [data attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*). Which, together with a bit of JavaScript, can be just as powerful.

First, let's attach some data attributes to our DOM elements in `public/index.html`.

```html
<div class="reddit_widget" data-subreddit="javascript"></div>
<div class="reddit_widget" data-subreddit="reactjs"></div>
```

Now, let's read those data attributes in our React App. There are a number of ways we can do this. 

1. We can use `Div.getAttribute("data-subreddit")` to get our attribute from each DOM element. We can pass this a `subreddit` prop to our React `<App/>` component.
1. Similar to option 1, but using the dataset property (IE: `Div.dataset.subreddit`).
1. We can pass the entire DOM element as a prop, to our React `<App />` component. Allowing us to access the entire DOM element for each App. From there, we can do anything with the dom element. Including getting the attributes. 

For more information, check out [using data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes).

For this article, We'll go with  option 3. 


```js
// index.js 

WidgetDivs.forEach(Div => {
  ReactDOM.render(
    <React.StrictMode>
      <App domElement={Div} />
    </React.StrictMode>,
    Div
  );
})
```

```js
// src/App.js 

function App({ domElement }) {
  const subreddit = domElement.getAttribute("data-subreddit")

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          My favorite subreddit is /r/{subreddit}
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}
```

![Reading DOM attributes in a React component](https://javascriptpros.com/wp-content/uploads/2020/08/Reading-DOM-attributes-in-a-React-component.gif)

Great! Now we are successfully passing data from the DOM to our React App. This opens the door to tons of possibilities. We can create entirely different versions of our app, based on the attributes passed from the DOM 😆

## Example of a "real world" reddit widget
[view final commit](https://github.com/GioLogist/article-react-reddit-widget/commit/8744511c32219e1012a34cd0eda5a98a4c5e5812)

For the sake of this article, I'll assume you're already familiar with a few basic React concepts. IE: [Data Fetching](https://reactjs.org/docs/faq-ajax.html) as well as [Components and Props](https://reactjs.org/docs/components-and-props.html). So I won't dive into the changes made to pull data from Reddit's API & display the lists. If you'd like a separate article on this, please comment below. However, I feel this is already covered extensively.

To make this widget even more useful and "complete", we'll fetch some data from Reddit's API. We want to include some of the latest posts, along with links to them. We also want to include a link to the subreddit itself. Finally, it's common practice for widgets to include a "powered by" notice. Especially in a "freemium" pricing model. This allows other people to discover your widget and also become customers. Maybe even paying customers.

Here's an example of what that looks like.


```js
import React, { useEffect, useState } from 'react';
import './App.css';

// Render each post
function renderPost(post){
  const { data: { title, url, author, id } } = post
  const authorUrl = `https://www.reddit.com/u/${author}`

  return (
    <div className="reddit_widget__post" key={id}>
      <div className="reddit_widget__posted_by">
        posted by <a href={authorUrl} className="reddit_widget__posted_by" target="_blank" rel="noopener noreferrer">u/{author}</a>
      </div>
      <a href={url} className="reddit_widget__title" target="_blank" rel="noopener noreferrer">{title}</a>
    </div>
  )
}

// Filter, since reddit always returns stickied posts up top
function nonStickiedOnly(post){
  return !post.data.stickied
}

function App({ domElement }) {
  const subreddit = domElement.getAttribute("data-subreddit")
  const [loading, setLoading] = useState();
  const [error, setError] = useState('');
  const [data, setData] = useState([]);

  useEffect(() => {
    // Fetch data from reddit
    setLoading(true)
    fetch(`https://www.reddit.com/r/${subreddit}.json`)
      .then((response) => response.json())
      .then((data) => {
        setLoading(false);
        setData(data.data.children.slice(0, 10));
      })
      .catch((e) => {
        console.log(e)
        setLoading(false);
        setError('error fetching from reddit');
      });
  }, [ subreddit ])

  return (
    <div className="reddit_widget__app">
      <h1 className="reddit_widget__header">
        Latest posts in <a href={`https://reddit.com/r/${subreddit}`} rel="noopener noreferrer">/r/{subreddit}</a>
      </h1>
      <div className="reddit_widget__inner">
        {loading && "Loading..."}
        {error && error}
        {!!data.length && data.filter(nonStickiedOnly).map(renderPost)}
      </div>
      <p className="reddit_widget__powered_by">
        This widget is powered by{" "}
        <a
          href="https://javascriptpros.com"
          rel="noopener noreferrer"
          target="_blank"
        >
          JavaScriptPros.com
        </a>
      </p>
    </div>
  );
}

export default App;
```

## Building our widget
[view final commit](https://github.com/GioLogist/article-react-reddit-widget/commit/7f6ee604a359bb8da7f96df23cf3430431410061)

We initialized our app using `create-react-app`. For the sake of getting our entire bundle into a single JS & CSS file, we'll build using parcel. Instead of completely replacing our build script, we'll add a new one called `build:widget`. In this article, we won't dive too deep into how parcel works, but feel free to [check it out](https://parceljs.org/).

First, add parcel as a dependency

```bash
yarn add --dev parcel-bundler
```

Update package.json with a new build script. This tells parcel to build our JS (which will also build our css) into our `docs` directory. Source maps won't be needed, to keep our build small. We chose the docs directory, so that we can publish our widget using GitHub pages, but any directory works. 

```json
"build:widget": "parcel build src/index.js --no-source-maps -d docs",
```

You may also want to ignore the *cache* directory parcel uses in *.gitignore*

```plaintext
# .gitignore

# parcel 
.cache
```


## See our widget in action

The full code, including styling, can be seen [here](https://github.com/GioLogist/article-react-reddit-widget). You can also demo the widget itself [here](https://codesandbox.io/s/react-widget-xj78i?file=/index.html). 

And here's what that looks like 🧐

(note: at the time of writing, the image below is broken, [here](https://javascriptpros.com/wp-content/uploads/2020/08/full-reddit-widget.gif) is a direct link)

-- 

![Full Reddit Widget built with React](https://javascriptpros.com/wp-content/uploads/2020/08/full-reddit-widget.gif)
![Full Reddit Widget built with React](https://dev-to-uploads.s3.amazonaws.com/i/ci2akqbgcvnz9ucxqvj2.gif)


## Enabling non-developers to use our widget

When providing instructions to a customer on how to use the widget, we'd probably send them instructions that look something like this:

> Copy these 3 lines of code and replace SUBREDDIT_HERE with the subreddit of your liking. 
You can add more than one widget by duplicating only the 3rd line.

```html
<link href="https://giologist.github.io/article-react-reddit-widget/index.css" rel="stylesheet" />
<script src="https://giologist.github.io/article-react-reddit-widget/index.js"></script>
<div class="reddit_widget" data-subreddit="SUBREDDIT_HERE"></div>
```

## Things to consider

- React may not always be necessary, or the best tool for a smaller widget. If keeping bundle size down is your main priority, you may want to consider building your widget simply using vanilla javascript.

- If your widget is going to load its own css, be sure not to include style properties for common elements such as `html` and `body`. You don't want to override the styling on someone else's page. Keep your styling specific to only your widget.


Any questions? Feel free to drop a comment.