CS 312 - Software Development

Lecture 04 - More React

React and ...

React and Next.js

This semester, we will be using Next.js as our development framework.

Next.js offers us a number of features, such as zero config setup with Webpack and Babel for transpiling our sites and packing them up for deployment, built in virtual page routing, support for a number of different ways to mix styling and our components, and API routing for development of simple servers.

We can create a new Next.js project with npx create-next-app name-of-app. This creates an npm module, installs react and next, and initializes a git repository for us.

There are also scripts for running a development server with hot reloading (npm run dev), as well as building our project for production (npm run build) and starting up a production server (npm start).

Next has a collection of directories that is treats specially

pages

The pages directory is the heart of our website. Each JavaScript file in this directory should contain a React component and will be treated as a unique "page" of our site. The file name is used to create a route that can be bookmarked, linked to, and shared.

Inside of the pages directory, you will find a special file called _app.js. This is a special case, and will not be routed to. The component in this file is the root of our component hierarchy. We use this file to add global styling or memory to the site. For many projects, you will find that you have no need to touch it (you can even erase it, as Next.js will supply its own if it is missing).

There are many other features of the pages directory, such as dynamic routes, which will deal with at a later date.

public

The public directory is where we place static assets. These are the files that can be served without any processing, such as image files or pdfs.

Note that despite being in a different directory, they will be served as if they were in the top level directory of the web site.

pages/api

While most directories with the pages directory just allow us to create hierarchical routes, the api directory is special. The JavaScript files in this directory provide the API routes, which provide server functionality rather than client-side rendering.

We will return to these later. For projects that do not use API routes, this directory can safely be deleted.

src

The src directory is not created by create-next-app. I personally appreciate having one place to find all of the source code, so I like to add it. In the repositories we work with, I will add the src directory, and will move the pages and styles directories inside.

Other common directories inside of src will include components for our sub-component files, and lib for non-component JavaScript.

Note that we cannot move config files or public inside of src or next will not be able to find them.

You will find the Next version of the Color Picker on GitHub.

React and PropTypes

JavaScript is dynamically typed, and as such, it is easier to introduce type-errors than in a statically typed language. To catch typing errors there are JavaScript extensions like TypeScript and Flow that provide static type checking. React provides a form of dynamic type checking for props via PropTypes that runs in development mode.

Each component as a propTypes object that specifies validators for the props. PropTypes provides a wide range of potential validators. For example, for the color picker we could specify:

LabeledSlider.propTypes = {
  label: PropTypes.string.isRequired,
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
  setValue: PropTypes.func.isRequired,
};

The more specific we can make these requirements the more likely we are to catch type errors (generally true for all kinds of validation). Note that validation isn't the only purpose for providing PropTypes. Doing so is also a way of documenting the "type signature" of the component (analogous to a function signature in a statically typed language).

The need for oneOfType, and that the types could be inconsistent, is a "code smell", that is an approach that doesn't seem quite right. The value is conceptually an integer, however the underlying value type of HTML input elements is specified to be a string (even if it is an input of type "number"). Instead of allowing both, let's instead always convert the string to an integer.

To do so, let's adopt a "TDD-like" approach in which we first update the "test", the PropTypes, verify we have an error then fix that error. If we require value to just be a number (i.e. value: PropTypes.number.isRequired), we should see an error in the browser's JavaScript console like:

index.js:1446 Warning: Failed prop type: Invalid prop `value` of type `string` supplied to `LabeledSlider`, expected `number`.
    in LabeledSlider (at App.js:52)
    in ColorPicker (at App.js:81)
    in App (at src/index.js:6)

Then we can update the slider onChange callback to to parse the string into an integer:

onChange={(event) => setValue(parseInt(event.target.value, 10))}

Now we should no longer observe a PropTypes error. To eliminate the chance for obtaining fractional values from the slider I also explicitly set the step to be an integer.

React and CSS

How can we style our application?

Next.js supports a collection of different approaches.

  • We can include a static CSS file as an asset, i.e. the traditional approach. But this approach is not very modular and doesn't necessarily work well with a component-based design as we would to have merge the styles for all components.
  • We can "import" CSS files (using features of Webpack to bundle that CSS into the JavaScript file) for each component. The challenge is that by default the imported CSS exports all class names into global selector scope creating a potential for naming collisions. We can incorporate scope into the class naming schemes, but this can get unwieldy fast.
  • We can use CSS Modules. This is essentially a scoped version of importing the CSS directly into the JavaScript. At build time, the class names are altered to make sure they are unique to the module. Support for these are built into Next.js.
  • Implement CSS-in-JS. CSS-in-JS integrates styling into the components as JavaScript code. There are variety of ways to do this. The simplest way is to add styles into the JSX inline as attributes. There are also other solutions just as styled-components and styled-jsx (which is bundled into Next.js).

There are arguments to be made for all of these options. The subtleties of CSS are left as an exercise for the reader, but much of the debate about the best approach to CSS is a debate about separation of concerns. Separation of Concerns (SoC) will be a recurring topic this semester, but in short, SoC is a design principle that each "unit" in a program should address a different and non-overlapping concern.

In this context, a common SoC argument around HTML/CSS is that HTML should specify content (only) and CSS should specify the style (only), i.e. separate style from content. Proponents of CSS-in-JS also make a SoC argument, but that one component should be entirely separate from the others.

In this class, we will make use of CSS modules, which, to some extent, split the difference. The styles still live in dedicated CSS files, but they are clearly associated with their companion component.

We can use the ColorPicker as an example of how to use CSS modules.

We write the CSS as normal, creating classes for different visual elements:

.colorLabel {
  display: inline-block;
  width: 50px;
  text-align: left;
}

.colorSwatch {
  width: 100px;
  height: 100px;
  border: 1px solid black;
}

For ColorPicker.js, we would store this in a file called ColorPicker.module.css.

In ColorPicker.js, we then import the CSS:

import styles from './ColorPicker.module.css';

Note that we give this a name instead of just importing the file and ignoring it (styles isn't important, you can call it whatever you like)

To apply a CSS class to an element, we use styles.class. For example:

<div className={styles.colorLabel}>{label}</div>

This gives this div the class we called "colorLabel". In the rendered HTML, the class name has been adjusted to be unique:

<div class="ColorPicker_colorLabel__1fKYH">red</div>