AWS Amplify Removed Video Source

AWS Amplify removed video sources from HTML

A good amount the websites I’ve pushed to production in the past couple of years have all been hosted on-prem within my client’s data centers. However, not all of my clients have test environments. Up until recently I’ve been hosting my test sites with Server4You, a fantastic non AWS/Azure/GCP hosting company/provider. But with the rise of AWS and Azure I want to start venturing over that way to gain experience and play around with those providers some more.

I’m starting with a front end only React app that I’ve developed for a client which will ultimately live in their data center when the site goes live, but to test while we’re building the site I decided to give AWS Amplify a try. This service is awesome and hooks right into Gitlab for automated CI/CD with a couple button clicks. Awesome as it is, I did run into one problem: My video src references were stripped out during the CI/CD process.

The site has three HTML5 video references to mp4s that are hosted within the build folder at the root of the web project. When the AWS Amplify CI/CD process migrated my code over to the hosting web server all of my src references were blank – <video src="" />.. I tried referencing the videos in a couple different ways from the React app but nothing seemed to work.

To get the video links to work in AWS Amplify, I ended up creating a S3 storage account, uploading my videos to a S3 bucket , assigned the appropriate permissions then referencing the videos in code to pull from the S3 bucket which ended up fixing the problem.

I haven’t been able to find any documentation that explicitly says that video sources are removed but hosting the videos in S3 fixed the issue for me.

Create multiple .env files in a React app

Configuring multiple environment files for a React app is handy if you have different variables for each environment (local, dev, test and production) – API endpoints is a good example. Like the name suggests, a .env file allows you to create environment specific variables that you specify to be used in specific builds for each of your environments. My projects usually have the four different environments:

  1. Local – My local machine where I do all my development.
  2. Development – This is the server environment where we can try new code outside our development machines. This is a server environment that is configured similar to test and production but is reserved to break things. Generally this environment is filled with dummy data and can become a mess pretty easily as we test out new features and debug code.
  3. Test or staging – A copy of the production environment where end users, testers and other folks from the team do their testing. This environment usually has the same data (minus any PII/PHI data) that production has.
  4. Production – This is the live site. You know what this is.

With these four environments I have five environment files in my React app structure. Each with their own variables that relate to the specific environment.

  • .env
    • This is a placeholder file that only shows the structure the other environment files should follow. For example:
      • REACT_APP_API_HOST=API_HOST
        REACT_APP_WEB_HOST=WEB_HOST
        REACT_APP_BUILD=BUILD
  • .env.development.local
    • This is the file that I use for my local development. I have this file added to .gitignore so it’s not checked into source control. Every developer should create this file locally and configure it with their local information.
  • .env.development
    • Development server
  • .env.staging
    • Staging server
  • .env.production
    • Production server

Then by utilizing the env-cmd package I’m able to run a command like npm run build:production to create my production build which uses the variables defined in the .env.production file. Same goes for local, development and testing/staging.

How to configure multiple .env files

Here’s how to set up a React application to utilize multiple .env files and variables. This example uses Create-React-App, so YMMV depending on what you bootstrapped your React app with.

1. Start off by creating a .env file at the root of your project for each of your environments.

Multiple .env files in React

2. Install the env-cmd package into your project npm install env-cmd

3. Open your package.json file and inside the scripts node add a line for each environment you will be building. You should already have lines for start, build, test and eject. Each line is specific to the build and .env file. So, build:development is the command you’ll run to create your development build, build: staging for testing and build:production for production.

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "build:development": "env-cmd -f .env.development react-scripts build",
    "build:staging": "env-cmd -f .env.staging react-scripts build",
    "build:production": "env-cmd -f .env.production react-scripts build"
  }

4. Add the environment specific variables to your environment files.

REACT_APP_API_HOST=https://localhost:5001
REACT_APP_WEB_HOST=http:localhost:3000
REACT_APP_BUILD=Development.Local

5. Run your build to create a build with your environment variables npm run build:production

Now you should have the ability to build your React apps with environment specific variables. Hopefully this makes your deployments easier and more straightforward.

How To Create A Reusable Select List In React

This tutorial is on how to create a reusable dropdown list component in React. I broke the functionality down into two components — the actual select dropdown list component and the calling parent/calling component.

The parent component will be App.js and a component called DynamicSelect.js will handle the select list functionality. The entire source code project is on GitHub.

Let’s start with the DynamicSelect component. The DynamicSelect component is the component which will render an array of Seinfeld characters into select list and pass back the selected value via the props object to the parent component. When the onChange event is fired for the select list the event is passed into the handleChange function. This function will pass the selected value back to the parent (App.js) via the props object.

DynamicSelect.js Component

import React, {Component} from 'react';

class DynamicSelect extends Component{
    constructor(props){
        super(props)
    }

    //On the change event for the select box pass the selected value back to the parent
    handleChange = (event) =>
    {
        let selectedValue = event.target.value;
        this.props.onSelectChange(selectedValue);
    }

    render(){
        let arrayOfData = this.props.arrayOfData;
        let options = arrayOfData.map((data) =>
                <option 
                    key={data.id}
                    value={data.id}
                >
                    {data.name}
                </option>
            );

            return (
            <select name="customSearch" className="custom-search-select" onChange={this.handleChange}>
                <option>Select Item</option>
                {options}
           </select>
        )
    }
}

export default DynamicSelect;

App.js Component

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import DynamicSelect from './DynamicSelect';

const arrayOfData = [
  {
    id: '1 - Jerry',
    name: 'Jerry'    
  },
  {
    id: '2 - Elaine',
    name: 'Elaine'    
  },
  {
    id: '3 - Kramer',
    name: 'Kramer'    
  },
  {
    id: '4 - George',
    name: 'George'    
  },
];

class App extends Component {
  constructor(props){
    super(props)
    this.state={
      selectedValue: 'Nothing selected'
    }
  }

  handleSelectChange = (selectedValue) =>{
    this.setState({
      selectedValue: selectedValue
    });
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Dynamic Select Dropdown List</h1>
        </header>
        <p className="App-intro">
          <DynamicSelect arrayOfData={arrayOfData} onSelectChange={this.handleSelectChange} /> <br /><br />
          <div>
            Selected value: {this.state.selectedValue}
          </div>
        </p>
      </div>
    );
  }
}

export default App;

And that’s it. The repository for this code is available on GitHub in this class fashion. This functionatliy can be easily replicated via functional components with hooks as well.

Mapbox GL JS Access Denied In IE 11

Mapbox gives you the ability to load custom icons for different points in your maps. When I tried to load a custom icon to a Mapbox map I got the error “SCRIPT5: Access is denied.” in IE 11 – Firefox and Chrome loaded the icon fine.  My code was loading an imported image as an object inside a React component like this:

import centerImage from "./images/centerImage.png";

map.on('load', () => {
    map.loadImage(centerImage, (error, image) => {
        if (error) {
            return;
        }
        map.addImage('centerIcon', image);
    });
    ...
});

Updating the image URL parameter to a string URL fixed the problem. I didn’t dig too much into the issue to figure out why this was happening, but passing a valid image string URL was enough to get passed this issue.

map.on('load', () => {
    map.loadImage('/hospital.png', (error, image) => {
        if (error) {
            return;
        }
        map.addImage('centerIcon', image);
    });
    ...
});

React + Mapbox GeoJSON Example

Here’s an example of how to create a Mapbox map in React using a GeoJSON data set. When I started building my first React Mapbox map I built them around some of the other pre-built components out there like ReactMapboxGL or react-map-gl. Both of these wrappers are great but eventually figured out I was better able to control all the Mapbox features on my own by targeting the Mapbox GL JS libary directly vs using a another component.

This example uses React hooks but could easily be updated for a class component if that is what you are working with. Below is one component called Districts that loads Minnesota’s eight congressional districts from a separate geojson file. After loading the districts they are given a unique fill-color and added as a layer.

You can view the entire React Mapbox GeoJSON example project in the GitHub repository or you can view a live example at clintmcmahon.github.io/react-mapbox-example

Districts.js React Component

import React, { useState, useEffect, useRef } from "react";
import mnDistricts from "./data/mn/mn-districts.geojson";
import ReactDOM from 'react-dom';
import mapboxgl from 'mapbox-gl';

function Districts(props) {
    mapboxgl.accessToken = process.env.REACT_APP_MAPBOX_KEY;
    const mapContainer = useRef(null);
    const [long, setLong] = useState(-94.503809);
    const [lat, setLat] = useState(46.443226);
    const [zoom, setZoom] = useState(4.5);
    const [hoveredDistrict, _setHoveredDistrict] = useState(null);
    const hoveredDistrictRef = useRef(hoveredDistrict);

    const setHoveredDistrict = data => {
        hoveredDistrictRef.current = data;
        _setHoveredDistrict(data);
    };

    useEffect(() => {

        let map = new mapboxgl.Map({
            container: mapContainer.current,
            style: "mapbox://styles/mapbox/light-v10",
            center: [long, lat],
            zoom: zoom
        });


            // Add zoom and rotation controls to the map.
            map.addControl(new mapboxgl.NavigationControl());
        map.once("load", function () {

            map.addSource('district-source', {
                'type': 'geojson',
                'data': mnDistricts
            });

            map.addLayer({
                'id': 'district-layer',
                'type': 'fill',
                'source': 'district-source',
                'layout': {},
                'paint': {
                    'fill-color': [
                        'match',
                        ['get', 'CD116FP'],
                        '01',
                        '#5AA5D7',
                        '02',
                        '#02735E',
                        '03',
                        '#00E0EF',
                        '04',
                        '#84D0D9',
                        '05',
                        '#202359',
                        '06',
                        '#CE7529',
                        '07',
                        '#00AE6C',
                        '08',
                        '#0056A3',
                        /* other */ '#ffffff'
                    ],
                    'fill-opacity': [
                        'case',
                        ['boolean', ['feature-state', 'hover'], false],
                        .8,
                        0.5
                    ]
                }
            });

            map.on('mousemove', 'district-layer', function (e) {
                if (e.features.length > 0) {
                    if (hoveredDistrictRef.current && hoveredDistrictRef.current > -1) {

                        map.setFeatureState(
                            { source: 'district-source', id: hoveredDistrictRef.current },
                            { hover: false }
                        );
                    }

                    let _hoveredDistrict = e.features[0].id;

                    map.setFeatureState(
                        { source: 'district-source', id: _hoveredDistrict },
                        { hover: true }
                    );

                    setHoveredDistrict(_hoveredDistrict);
                }

            });

            // When the mouse leaves the state-fill layer, update the feature state of the
            // previously hovered feature.
            map.on('mouseleave', 'district-layer', function () {
                if (hoveredDistrictRef.current) {
                    map.setFeatureState(
                        { source: 'district-source', id: hoveredDistrictRef.current },
                        { hover: false }
                    );
                }
                setHoveredDistrict(null);
            });

        });

    }, []);

    return (
        <div className="district-map-wrapper">
            <div id="districtDetailMap" className="map">
                <div style={{ height: "100%" }} ref={mapContainer}>

                </div>
            </div>
        </div>
    );
}

export default Districts;

Minnesota Congressional Districts GeoJSON

This geojson data set represents Minnesota’s eight congressional districts that I pulled from the US Census. Each feature has a property “CD116FP” that I’m using to set the fill-color of each district layer. The GeoJSON data is so big I didn’t include it in this blog post but you can download it from the Github repo.

You can view the source on Github.

That’s it. Happy coding!