RFR Section 13 – Getting Ready to Go Live


React

Updated Mar 14th, 2022

Table of Contents

Course Summary

Chapter Details

Ch. 72 – React Suspense Lazy Loading Part 1

Don’t want people to download components or files they don’t need. Lazy-loading is not a new idea but the tool called “Suspense” is super easy to implement.

In the “Main.js” file we start with the “CreatePost” component by swapping the existing import line for:

const CreatePost = React.lazy(() => import("./components/CreatePost"))

Note: When our app first loads, “CreatePost” will not contain the content of the component, but will instead contain a promise. So we need to be aware of how we are actually using “CreatePost” down in the JSX.

Import “Suspense” from “react” at the top of the “Main.js” file. Leverage Suspense by wrapping a “Suspense” component tag around the entire “Switch” and on the opening tag give it a prop of “fallback” set to “{ <LoadingDotsIcon />}.”

Import the “LoadingDotsIcon.js” file at the top of the “Main.js” file.

Note: You wrap a “Suspense” tag around any component that is being loaded, although, here, we just wrap around entire “Switch.”

Save and test after a manual browser refresh on the homepage. Watch in the network tab after hitting the “Create Post” button in the header, you will see another file is loaded.

Now do the same for other components. Make “ViewSinglePost” lazy-loaded by swapping it’s import line at the top of the “Main.js” file. And that’s it.

The main bundle file becomes smaller and smaller for each component that is lazily loaded.

We don’t want to lazy load everything. For example, if it’s small file and/or a small percentage of people will never click it (like the about page), it may not need to be lazy loaded. Deciding what should or shouldn’t be lazily-loaded is subjective but weigh both how large the file is and how likely it will be needed.

Ch. 73 – React Suspense Lazy Loading Part 2

Lazy loading the “Chat” and “Search” components is a bit more complicated since they don’t live within the “Switch” wrapped in the “Suspense” tags. Duplicate an existing “React.lazy()” import line and tweak for “Search.”

Note that the “Search” component is only 4kb so it doesn’t need to be lazy-loaded but we will here so we can see implementation of a component that is not in the “Switch” and also wrapped in “CSSTransition” tags.

Note: This is not the only way to implement lazy-loading for a component in a “CSSTransition” tag.

Still in the “Main.js” file, down in the JSX for the “Search” component you will see it is wrapped in “CSSTransition” tags. The “CSSTransition” tag will add the necessary classes to it’s direct child element. if the direct child is a “Suspense” component, or a component that hasn’t been loaded yet, it cannot add classes to something that doesn’t make sense.

Remove the “Search” component and add a “div” element with a “className” of “search-overlay.”

Go into the “Search.js” file, and remove this “div” element from that JSX by converting it into a “React fragment” symbol.

Back in the “Main.js” file, inside of this new “div” have “Suspense” tags and inside of that have your “Search” component. On the opening “Suspense” tag give it a “fallback” prop set to an empty string to do nothing for a split second or two.

So the Search component will now be lazy-loaded and we want to do the same thing for the “Chat” component but we want to take it a step further and only show if a user is logged in.

Start by duplicating an “import React.lazy()” line and tweaking for “Chat.” Down in the JSX, temporarily get rid of the “Chat” component and add “Suspense” tags and inside conditionally load by having “{state.loggedIn && <Chat />}.” On the opening “Suspense” tag set the “fallback” prop to be an empty string.

<Suspense fallback="">{state.loggedIn && <Chat />}</Suspense>

Test it out as a logged in user. You will see two files since “socket.io” loads as well. Test to make sure if you are not logged in that “Chat” is not loaded.

When the “Chat” component unmounts, we want to make sure the “socket.io” connection disconnects when you logout by going into the “Chat.js” file and finding the “useEffect” where the dependency array is empty. Before the very end of this function return a cleanup function that says “socket.disconnect().”

Now make sure if someone logs out and then logs back in we need them to re-establish the “socket.io” connection. At the top of the “Chat.js” file copy the code to right of the equal sign for the constant variable “socket.” and then delete the entire “const socket” line.

Instead, in the overall “Chat” function have a constant variable named “socket” equal to “useRef(null).” We’re using “useRef” instead of “useState” here because we don’t want React to recreate this variable or micromanage it, we just want a basic mutable object that’s not going to change so that the web browser can consistently hold on to the socket connection.

Back down in the “useEffect” with the empty dependency array, at the very start of this function, open the connection by saying “socket.current” and setting equal to what’s in your clipboard. In order for the function to work correctly, in the same function, where it says “socket.on,” change this to “socket.current.on.”

Do the same thing where it says “socket.disconnect” by making this “socket.current.disconnect.” Do this one more time down below, in the “handleSubmit” function, where it says “socket.emit,” make it “socket.current.emit()”

Ch. 74 – Note About Suspense for Data Fetching

In addition to lazy loading our components, “suspense” and React will also offer us a new and elegant way of handling the timing and user experience of fetching data. However, as of today this feature is not yet in the official current version of React. Once “suspense” for data fetching is officially added into React you will want to implement it. Until then don’t wouldn’t worry about it.

Ch. 75 – Building a “Dist” Copy of Site

We want to create a copy of our site ready to be delivered and consumed by the public.

The “app” folder contains source files that we work on in development mode that are very large.

Create a new “dist” folder with only what the public really needs, with the smallest files as possible. Contents of this folder is what will be put on a live web server.

Create a new “Webpack.config.js” file and paste in code from the course repo. Here is the link.

Install “dotenv-webpack, clean-webpack-plugin, html-webpack-harddisk-plugin, html-webpack-plugin, and fs-extra” libraries.

Go into the “index.html” file and remove line at the bottom where we manually include our bundle file because new our new webpack setup will auto-inject this and add a cache-busting string.

Create a “.env” file in the root of your project, create a “BACKENDURL” envronment variable use this in the “Main.js” file.

// in .env file

BACKENDURL = http://localhost:8080


// in Main.js file

Axios.defaults.baseURL = process.env.BACKENDURL || ""

In the empty string, we will paste a value from Heroku here later.

Make sure the “dev” script still works by running “npm run dev.”

Now create a new script to build the “dist” folder. In the “package.json” file, create a “webpackBuild” script that runs “webpack,” (instead of “webpack-dev-server).”

When you run this you should have a new “dist” folder with your “main.css” file and “index.html” file, and all your other files where the other files all have with cache-busting strings.

The final task in this lesson is to preview the “dist” build because you can’t just drag “index.html” onto a web browser window. Create script in the root of your folder and name it “previewDist.js” and copy and paste a few lines of code from the course GitHub repo and paste in. Before testing this out, install the “express” package.

const express = require("express")
const path = require("path")
const app = new express()
app.use(express.static(path.join(__dirname, "dist")))
app.get("*", (req, res) => res.sendFile(__dirname + "/dist/index.html"))
app.listen("4000")

In the “package.json” file, add one more script called “previewDist” that runs “node previewDist.” Test it out by running “npm run previewDist” and then go to “localhost:4000,” (it as set out so we could run at the same time).

Now that we have the “dist” folder, this contains the exact files that you will push up to the web so you can check them and test them and make sure things are working.

Note in your project directory, in the app folder, this new webpack setup has created an “index.html” file, whereas we only created “index-template.html” file. So if you ever want to make changes to your html skeleton, you modify “index-template” and webpack will use that to automatically generate the “index.html” file.

Ch. 76 – React Outside of the Browser Part 1

The reason why we need to do this is because if we open the network tab, slow down our connection, and disable the cache, the site takes forever to load and there’s just a white screen. The web browser doesn’t have anything to display while it takes time loading the JavaScript file. So we want to do better as developers and give the user at least something that looks like a website, and they can tell the page is loading.

Create an automated task that generates a html file and pulls from the existing component files. Create a new “generateHtml.js” file (node task) in the root directory. Paste in code from the course repo.

In this file we have a “Shell” component whose structure has some importance. Wrapped in Router and Context tags to prevent an error.

function Shell() {
  return (
    <StateContext.Provider value={{ loggedIn: false }}>
      <Router>
        <Header staticEmpty={true} />
        <div className="py-5 my-5 text-center">
          <LoadingDotsIcon />
        </div>
        <Footer />
      </Router>
    </StateContext.Provider>
  )
}

At the bottom of the file we use NodeJS to create a file.

/*
  This course is not about Node, but here we are simply
  saving our generated string into a file named
  index-template.html. Please note that this Node task
  will fail if the directory we told it to live within
  ("app" in this case) does not already exist.
*/
const fileName = "./app/index-template.html"
const stream = fs.createWriteStream(fileName)
stream.once("open", () => {
  stream.end(overallHtmlString)
})

The most important line of code in this file is:

// in generateHtml.js
// import ReactDOMServer from "react-dom/server"


const reactHTML = ReactDOMServer.renderToString(<Shell />)

In the “Shell” component, in the JSX, in the “Header” component, there is a prop called “staticEmpty” that is set to true. This is a made up name that helps us render the top navigation bar to be completely empty besides the logo. To make this work go to the “Header.js” file, and select everything in the line that says “appState.loggedIn” and cut that into your clipboard. At the top of your “Header” function, create a constant variable named “headerContent” and set it equal to what is in your clipboard by pasting back in.

const headerContent = {appState.loggedIn ? <HeaderLoggedIn /> : <HeaderLoggedOut />}

Where that line previously existed, have the following code:

{!props.staticEmpty ? HeaderContent : "" }

In the “package.json” file, create a new run command “script” called “generate” to create the shell.

// in package.json" file

"generate": "babel-node --presets=@babel/preset-react, @babel/preset-env generateHThtml.js"

Before we run this we have to install the package “@babel/node” and now we can take this script for a test drive.

Ch. 77 – React Outside of the Browser Part 2

We need to set things up so whenever we run “npm run dev” or “npm run build,” the “generateHTML” script runs first.

Duplicate the existing “dev” script and rename the second copy to be “webpackDev.”

In the original “dev” script have “npm-run-all” and then “-s” for sequential, instead of in parallel, and then run “generate” and then run “webpackDev.”

"dev":"npm-run-all -s generate webpackDev"

“npm-run-all” is not a native part of nodeJS so you have to install the package aptly-named “npm-run-all”.

Create another script to run the build copy of the site by creating a new script called “build” that runs “npm-run-all -s generate webpackBuild.”

Note: The most importing concept here is “ReactDOMServer.renderToString(<Shell/>)” where we generate a string of html based on our React components. Very powerful. Next JS and Gatsby is mentioned here.

Ch. 78 – Pushing Our API Backend Server Up To The Web:

Heroku

Create new app with unique name

Heroku forces you to use git to send the files to them

Push the files to a private GitHub repo and connect your repo to Heroku account.

Add a “.gitignore” file before pushing to ignore the “.env” file and the “node_modules/” folder.

You can push to git by using URL that ends in “.git” or set up an SSH key although apparently, “almost everyone finds that to be incredibly frustrating the first time they do it.”

git remote add origin pasteURLHERE.git

Back in Heroku, give them GitHub access and select the appropriate repository and branch. For the first deployment, you need to click “deploy app” button.

Set environment variables manually in the Heroku dashboard.

Ch. 79 – Pushing Our React Front-End Up To The Web

In the “Main.js” file, in the empty string in the “Axios.defaults.baseURL” line, paste in the URL (including https) of your backend on the Heroku hosting service.

Note: you have to delete the forward slash at the end of the heroku url.

In the “Chat.js,” in the “useEffect” that calls “io,” and passes it a web address, have the same “process.env.BACKENDURL || “youHerokuBackendURLHere” string as in the “Main.js” file.

Before deploying the front-end to the Netlify hosting service, create a “.gitignore” file that includes, “.env”, “dist/”, and “node_modules/” to the file. We ignore the “dist” folder because Netlify performs our build task for us.

Also create a “netlify.toml” file where we tell it to use our “index.html” file for all URLs, since our application uses client-side routing. In this “netlify.toml” file we can do that with:

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

Run “git init,” create a commit, and push to new private GitHub repository. In Netlify, connect to your GitHub and select the necessary repository.

The “build command” is an important option to set correctly, (This is “npm run build” with no quotes for this course).

The “publish directory” is an important option to set correctly as well, (“dist” with no quotes for this project).

Netlify is great because, for future deployments, just push to the repo and they handle everything.

Note: The free version of Heroku spins down the server if no one visits the site in half an hour. The first time a user visits the site after this “sleep” there will be a short delay to load the website and this is normal.