Importing image files in Create React App

We had a problem serving image files in our application built with Create React App this week because we were trying to access them from our public folder in our JavaScript files that lived in our src > components folder.

The first error we got was: You attempted to import ../../public/images/001-saloon.png which falls outside of the project src/ directory. Relative imports outside of src/ are not supported. The first way to fix this was to move the images into the src folder.

return (
  <img src="./images/cat.jpg" alt="" />
)

Then we needed to import the file path as outlined in the Create React App docs:

import catImgSrc from "./images/cat.jpg"

return (
  <img src={catImgSrc} alt="" />
)

Testing in React

Today we were practicing running tests on our React apps using Jest. To mimic the browser APIs that interact with the DOM Jest comes with jsdom set-up, which we use React Testing Library (RTL) to render our React components to.

We can import methods from RTL to render, access and fire events to the jsdom.

import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import ToggleButton from "/filename";

test("The button changes text when clicked", () => {
  render(<ToggleButton>click me</ToggleButton>);
  // renders the React component
  const buttonNode = screen.getByText("click me");
  // finds an element with the specified text content
  fireEvent.click(buttonNode);
  // triggers the click event
  screen.getByText("just clicked");
  // checks if the same element now has the updated text content
});

Expressions vs statements

If you know how to use a programming language to build things, it can be easy to run away with your code without understanding the fundamentals behind why the syntax you used is working (hence why there is a whole book series called You Don’t Know JS). Until you hit an error of course!

Today we were practicing conditional rendering in React and it led us on a back-to-basics discussion about the difference between expressions and statements. I asked myself if I would be able to explain the difference to someone else and I wasn’t completely sure, so I decided to give myself an explicit reminder today.

In JavaScript expressions are any unit of code that can be evaluated to a value, so can appear anywhere where values are expected.

1 + 1; // arithmetic expression
"hello" + "world"; // string expression
1 < 2; // logical expression
"hello world"; // primary expression - a string literal

Statements do not resolve to values, they are instructions to perform actions.

const sum; // declaration statement

function greeting(message) {
  console.log(message);
} // function declaration statement

if (time < 18) {
  greeting("good day");
} else {
  greeting("good evening");
} // conditional statement - executes based on the value of the expression time < 18

In JavaScript you can write an expression where a statement is expected, but not the other way around.

console.log(const a); // trying to pass a statement as a function argument results in an error

React: Creating elements with nested JSX

This week we are learning React, and today we were covering the syntax for rendering HTML elements using JSX syntax.

If you want to render multiple elements in React, your JSX must return a single parent element that wraps the nested elements.

<!DOCTYPE html>
...
<main id="root"></main>
...
</html>

<script type="text/babel">
const root = document.getElementById("root");
const App = () => (
  <div>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
    <p>Paragraph 3</p>
  </div>
);

{/* Invalid JSX
const App = () => (
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
  <p>Paragraph 3</p>
);
*/}
   
ReactDOM.render(<App />, root);
</script>

Redirecting your SPA on Netlify

In our project this week we built a single-page application. We wrote a custom “missing” page to display when the user routes to a non-existent page and gets a 404 HTTP response. However, when we deployed on Netlify the user gets server the default Netlify missing page which isn’t customised.

In the Netlify docs, the method to redirect routes on your application is to add a file with the name _redirects which outlines the rules for your paths.

Having the below in your _redirects file will redirect any non-existing paths to your missing.html page with a status code of 404:
/* /missing.html 404

Having the below in your _redirects file will redirect any non-existing paths to your index.html page with a status code of 200:
/* /index.html 200

Configuring servor npm package

This week we used the servor npm package to serve our single-page applications with front-end routing so we could render all our endpoints from one index.html.

I was having trouble running our application server locally on the correct port. The syntax from the servor docs is that it needs to know the following: <root> <fallback> <port>, so I had to add the below to my package.json file.

"scripts": { 
  "dev": "servor . index.html 3000" 
},

Making elements clickable and still accessible

If you have an element like a “card” on your webpage, and you want the whole element to be clickable, you might be tempted to wrap the link around the element like this:

<a href="https://www.google.com/" aria-label="link to google">
  <div class="card">
    <h2>Google</h2>
    <p>A popular search engine.</p>
  </div>
</a>

However, this solution is not ideal for screen readers because the aria-label may prevent the screen reader from reading the contents of the element. Rather than wrapping anchor tags around elements, you can use CSS to make the whole element clickable:

<div class="card">
  <h2>
    <a href="https://www.google.com/" aria-label="link to google">Google</a>
  </h2>
  <p>A popular search engine.</p>
</div>
.card a::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

Where to put your <script> tags

Our practice so far in the course has been to put our <script> tags before the closing </body> tag in our HTML files. This is because when the browser encounters the <script> tag it blocks the parsing of the HTML and will not continue rendering the page until the JavaScript code has been downloaded and run. But the downside of including our JavaScript at the end of the HTML file is that the browser won’t start downloading the scripts at all until all of the page is parsed, which will affect the user experience if the files are very large.

In this week’s research spike we learned about the Critical Rendering Path, and how we can use the async or defer keywords in modern browsers to load our scripts in the <head> of our HTML files. These allow your scripts to be downloaded as soon as possible without blocking the HTML parsing.

Scripts with the async attribute are executed asynchronously in a casual order:
<script defer src="script.js"></script>

Scripts with the defer attribute are only executed after the entire document has been loaded:
http://script.js

Exporting modules in JS

So far in the course we have been doing server-side rendering for our projects with Node, but this week we are making single-page applications with client-side rendering.

Loading lots of <script> tags on our singular HTML file can slow the application down because each time a new script is encountered an HTTP request is made, and this blocks the rendering of the page.

Instead we can load our scripts as ES Modules, and the client will follow the import paths and execute each module once. We can do this by adding type="module" to the script tags on our HTML file.

<script type="module" src="app.js"></script>