Categories
React

Creating React “Widgets” that can be embedded on any website, by anyone

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

Some of examples of a widget are the related content widget by Taboola. Or the newsletter widget, by MailChimp. In this article, we’ll teach you how to build a reddit widget.

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.

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.

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

ReactDOM‘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.

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

Multiple Instanes of React

view final commit

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 and search for all divs with a reddit_widget class specified. Then, we’ll inject our React App into each of them.

// 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.

    <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

Passing Data Attributes

view final commit

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.

Reading DOM attributes in a React component

In React, we use Props to attach useful data to our components. In HTML, we have data attributes. 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.

<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.
  2. Similar to option 1, but using the dataset property (IE: Div.dataset.subreddit).
  3. 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.

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

// index.js 

WidgetDivs.forEach(Div => {
  ReactDOM.render(
    <React.StrictMode>
      <App domElement={Div} />
    </React.StrictMode>,
    Div
  );
})
// 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

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

For the sake of this article, I’ll assume you’re already familiar with a few basic React concepts. IE: Data Fetching as well as Components and Props. 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.

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

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.

First, add parcel as a dependency

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.

"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

# .gitignore

# parcel 
.cache

See our widget in action

The full code, including styling, can be seen here. You can also demo the widget itself here.

And here’s what that looks like 🧐

Full Reddit Widget built with React

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.

<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.

// happy hacking
Categories
DevOps

Deploying React Apps on Different Cloud Hosting Providers

In this article, we’ll cover & compare how to deploy React Apps on different Cloud Hosting providers. We’ll take you from start to finish. Including initializing your React App, signing up for the Cloud Hosting provider and deploying your React App.

We’ll be updating this article periodically. Below, is a list of the providers we plan on covering and the status on each.

Cloud Hosting Status
Firebase Hosting Covered in detail
Hostman Video Below. Article coming soon
Digital Ocean Coming soon
Netlify Coming soon
Zeit Coming soon

We take this "Evergreen" approach on some of our articles, in order to create ever-green pages and allow ourselves to pump out content right away. Instead of waiting till everything is perfect and never updating an article again, we want our articles to be a living, breathing piece of content that you can refer back to. With our evergreen articles, we have separate email lists (per article) to keep up-to-date. By subscribing below, you will only receive emails when this article is updated.

Keep up to date

Hostman

Categories
React Native

React Native: Comparing popular Component Libraries

Component libraries are naturally one of the first things developers reach for when starting a React Native project. There’s something slightly underwhelming about being excited to start a new project, only to find yourself building the same type of component over and over again.

When picking a component library, there are a few things to consider.

  • How friendly is the API? It’s easy to get caught up in the look and feel of a component library. However, any well developed component library should be relatively easy to customize to your liking. What’s perhaps more important is how comfortable you are with the API. Do you agree with the decisions made by those who made the library? Does it feel natural when using it for a bit? Or do you find yourself struggling to implement it, even after reading the docs and getting a better understanding? Always experiment with a few component libraries, to see which suits you best (that’s what we’ll be doing in this article : ) ).
  • Do you like the base design provided? Some React Native component libraries, such as material kit react native are very opinionated with the out-of-the-box styling they provide. Others, such as native base don’t focus too much on the styling. Rather, they just provide a set of basic components. Leaving the composition and styling of components up to you. Different requirements call for different component libraries.

The goal of this article is not to answer these questions for you. Instead, we’ll explore a few different React Native component libraries, in a way that’ll allow you to answer these questions for yourself. We’ll build a Card component, which is very common in any code-base. By constructing the Card component, we’ll interact with the API directly. Allowing us to determine how much we enjoy working with the component libary. Upon completion, we can take a step back and see if we agree with the base design provided.

Native Base

Native Base is a component library with a focus on a cross-platform "native" feel. Instead of making fancy design decisions for you, Native Base provides a set of basic components which are styled specifically to their platform.

For example, check out what a "Header" component looks like on iOS vs Android.

React Native Elements

React Native Elements is a component library that’s 100% community driven. It provides over 20 basic components, suitable to bootstrap any project. The components provided appear the same regardless of the platform. For example, a Header component looks the same on both iOS and Android.

Notice the difference in the React Native Elements Card below, compared to NativeBase above. In the React Native Elements Card Component, there’s a built in Image prop. This automatically inserts the image at the top of the card. There are ways to make our Card identical to the one we built above. However, let’s not try and force a specific design. Instead, let’s embrace the differences between the two packages. Allowing us to get a better feel for the API & design decisions made.

UI Kitten

UI Kitten is a component library with a strong emphasis on theming and design systems. All of this is highly configurable, but it comes at a cost. When setting up UI Kitten on your react native app, there are additional steps involved to integrate the component library. You must wrap your app in an ApplicationProvider component. This is where you define the theme in which it will be using. UI Kitten allows you to use their very own "Eva" theme, or a "Material" theme. Both come with light & dark versions as well.

Additionally, the documentation for UI Kitten is beautiful and has live previews every step of the way. The live previews even allow you to switch between themes and light/dark mode. Check out the Card component documentation, for example. And of course, check out the Card component we made below πŸ€“

More component libraries being added soon!

You can expect this post to be updated with more component libraries as time passes. We will be updating this regularly : )

Want to be notified whenever we add more component libraries? Subscribe here.


Have a React Native component library you’d like added here? Drop us a message

Categories
DevOps

(CI/CD) Multi-Environment deployment for a React App on Firebase, using GitHub Actions

Deploying to Firebase has never been easier. Here, we’ll cover how to automate your workflow, in order to test & deploy to multiple environments. This will assist in rapid iterations to your app, without constantly pushing breaking changes to production. The goal is to have an environment for your users (production), an environment to approve changes prior to shipping (staging) and an environment that’s actively being worked on (development).

By the end of this article, your app will automatically be tested & deployed to a specific Firebase Project (one per environment) simply by pushing to GitHub. Although we go extremely in-depth in this article, the end result is rather simple. The commits can be seen here and are kept to one commit per step.

For the sake of this article, we’ll assume you’re unfamiliar with a few terms.

  • Multi-Environment Deployment: The standard practice for developing & testing changes in an isolated environment.
  • CI/CDContinuous Integration is the practice of merging all outstanding changes in a codebase, as often as possible. Being sure to test at all points. Continuous deployment is the practice of automating deployments to your users. Getting them new updates regularly.
  • Firebase: A mobile and web application development platform, owned by Google, that provides a number of products for any real world app. In this article, we’ll only be using β€œFirebase Hosting”.
  • Github Actions: Allows you to create different workflows in order to automate certain tasks, such as CI/CD.
  • GitFlow: A widely adapted structure for naming your git branches.

There are a few articles on deploying a React App to Firebase, using GitHub Actions. This article aims to build upon that, adding multi-environment deployment. In addition, we opted not to use any pre-existing GitHub Actions for Firebase, in order to reduce the level of abstraction and allow you to see exactly what’s happening.

Prerequisites

  • Firebase Projects set up. You will need one project per environment. (IE: App Dev, App Staging, App Production)
  • Firebase CLI installed
  • GitHub repo, for running actions.

If you enjoy diving deeper to get a better understanding of what’s happening, I recommend you take a look at Github ActionsConfiguring a workflowWorkFlow SyntaxContexts and Expressions.

Step 1) Initialize your react app

view commit

Nothing special here, feel free to skip this, if you’ve already got your react app set up.

npx create-react-app myapp

Step 2) Initialize Firebase Hosting

view commit

cd myapp
firebase init
# Which Firebase CLI features do you want to set up for this folder?
# Firebase Hosting
# Select a default Firebase project for this directory
# (recommend you use your "dev" project for now, but any is fine)
# What do you want to use as your public directory?
# build
# Configure as a single-page app (rewrite all urls to /index.html)?
# y
# File public/index.html already exists. Overwrite?
# n

Now, merge the .gitignore file Firebase uses with the one React automatically created. You can find the file here. Just copy and append it to the one you have locally. This is needed, at the time of writing, since Firebase doesn’t automatically overwrite your current .gitignore.

Step 3) Initialize CI&CD w/Github Action

view commit

First, you’ll need to generate a Firebase CI token. This is needed in order for GitHub to authenticate to Firebase. Allowing you to deploy.

firebase login:ci

Caution: this token can be used to perform almost any task on any Firebase project you have access to. Save it. If, for any reason, your token becomes compromised, you can run the following command: firebase logout --token YOUR_TOKEN

Now, you’ll need to add your Firebase token to your repo’s secrets. Navigate to your GitHub repo, go to β€œSettings -> Secrets -> Add a New Secret”.

Name: CI_FIREBASE_TOKEN

Image for post
Github Secrets

Note: Alternatively, you can name this FIREBASE_TOKEN, if you want to avoid passing the token explicitly during the deployment. more here.

Now that we’re all set up, let’s create our initial workflow. Create a file at .github/workflows/firebase_web.yml, with the contents below.

Workflow explained

Curious as to what’s happening? We’re creating a workflow that’s triggered whenever a push is made. The workflow runs on Ubuntu. We checkout the current branch, this way the code we’re working on is the one that’s executed. Then, we install Node, which is needed by Firebase. After that, we run yarn (npm can also be used just fine) to install dependencies. Before deploying, it’s always best to test. We do that with the same test script React ships with. Only this time, we make sure to specify `CI=true`, which tells the React script that we’re working in a [CI environment](https://create-react-app.dev/docs/running-tests/#continuous-integration). From there, we run the typical β€œbuild” command that React ships with. After the build is complete, we deploy using Firebase CLI. We explicitly pass in our token (for authentication) & a message. The message consists of the GitHub commit message and a link to the workflow ran. This message will show in the Firebase console and will be useful, in knowing which commit triggered the current deployment.

Now, add firebase-tools as a dev dependency in your package.json

"devDependencies": {
    "firebase-tools": "^7.7.0"
},

You should be able to push to GitHub and see a successful action now : )

git add .
git commit -m 'Step 3) Initialize CI&CD w/Github Action'
git push

Success πŸš€

Image for post
Github Workflow

And if we head on over to Firebase Console -> Hosting, we’ll see our latest deployment along with the message we configured ⚑️

Image for post
Firebase Hosting β€” Release History

Brief Recap

In Steps 1–3, we developed a workflow which is capable of automating our testing & deployment. If we were only concerned about one environment, our work would be done. Next, we’ll cover extending that workflow to multiple environments. We’ll follow the common pattern of 3 environments: development, staging, production.

Step 4) Implement Multi-Environment support

view commit

Option A) With GitFlow

This is the approach we recommend, as we enjoy the structure GitFlow provides. However, it does come with the added overhead of your team understanding GitFlow. If you’re not comfortable with this, feel free to use the simpler Option B below.

Here’s an outline of which branches we’d like to deploy to which environments, following GitFlow conventions.

╔═════════════════════╦═══════════════════════╦══╗
β•‘       Branch        β•‘ Environment to Deploy β•‘  β•‘
╠═════════════════════╬═══════════════════════╬══╣
β•‘ feature/**, develop β•‘ dev                   β•‘  β•‘
β•‘ release/**          β•‘ staging               β•‘  β•‘
β•‘ master              β•‘ production            β•‘  β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•

If you haven’t already, initialize GitFlow. Then, start a feature branch.

git flow init
## We kept all default options. 
git flow feature start multi-environment-support

Update your workflow (firebase_web.yml) to utilize GitFlow style branches.

Finally

Let’s edit our .firebaserc to include our 3 project aliases, instead of just a “default”. This allows us to specify the project during deployment, via the -P argument.

{
  "projects": {
    "development": "myapp-dev",
    "staging": "myapp-staging",
    "production": "myapp-production"
  }
}

You should be able to push to GitHub and see a successful action now : )

git add .
git commit -m 'Step 3) Initialize CI&CD w/Github Action'
git push

Additionally, any pushes you make should deploy to the correct Firebase project, based on the branch you’re working on ⚑️

Image for post
Firebase Hosting β€” Release History

All done!

Congratulations! You’re now ready to maximize your workflow by utilizing automated deployments to multiple environments πŸš€πŸ€™πŸ»


Optional Steps…

Step 5) Environment variables in your React App

view commit

Let’s say you wanted variables that vary depending on the environment they’re in (environment variables). This can be useful for things such as API keys, enabling/disabling features and more. React automatically configures any environment variables prefixed with REACT_APP_during build time.

To add environment variables to your React App, simply specify them in your workflow. Since you want the environment variables to be different for each environment, you’ll want to define them per deployment step.

For example, edit firebase_web.yml to include the following:

- name: Build & Deploy Dev
  ## Configure environment variables (do this on each build step)
  env:
    REACT_APP_NAME: My App - Development

These variables will now be accessible to Github. You’ll likely want them accessible locally as well. React utilizes .env files for this.

Add .env.local to the root of your app, with the following contents

REACT_APP_NAME=My App - Local

Note: .env files are already ignored in .gitignore, so we don’t have to worry about them accidentally being committed and overwriting our CI/CD steps.

Environment variables can be accessed in a React App via process.env. For example, let’s try using the one we just set up.

Edit src/App.js

<img src={logo} className="App-logo" alt="logo" />
<!-- add below -->
<h1>{process.env.REACT_APP_NAME}</h1>

Now, start your app

yarn start

Success! πŸš€

Image for post
React App, with Environment Variable

Note: Since environment variables are implemented during build time, this means that any changes made to your .env files won’t be reflected until the next time you run yarn start or yarn build.

Happy Hacking! πŸ€“πŸ€˜πŸ»

Have any questions about this article, or want to discuss a project? Feel free to reach out to us at For Makers, by Makers.

Interested in the final result? Take a look at these GitHub commits.