React: Structure, Components, and Fragments

Every React application is going to be structured a little bit differently, but there are some common conventions I want to highlight in today's blog post. Let's assume you have a React application named react-app, and in its directory you have something like the following:

react-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   └── index.html
└── src/
    ├── App.js
    └── index.js

This is a pretty bare-bones example, but it's essentially a simplified version of what you get out of the box from Create React App. You'll get the usual web development stuff – a README.md, a node_modules directory, and a .gitignore, but I want to focus on the three things that serve as the sort of entry point for your React app: package.json, the public directory, and the src directory.

package.json

As with just about any other modern JavaScript-driven programming environment, the entry point to your application is package.json. This will define dependencies (like react and react-dom as covered in the previous post), and scripts to actually run the app.

Typically under the hood React runs Webpack to process your files, transpiling JSX down to the bundled JavaScript that gets embedded into the actual application into the index.html file, which is found within the public directory.

public Directory

The public directory is where public-facing assets live. These are static assets that the final application will use, but that don't really need to be generated by Webpack.

When you use a React-based static site generator, like Gatsby, the generation/build step will compile your application down to static HTML, CSS, and JavaScript files and write those files to your public directory. This produces a structure that may look something like:

react-app (simple Gatsby example)
└── public
    ├── index.html
    ├── styles.css
    ├── index.js
    ├── about
    │   ├── index.html
    │   ├── styles.css
    │   └── index.js
    ├── contact
    │   ├── index.html
    │   ├── styles.css
    │   └── index.js
    └── blog
        ├── index.html
        ├── styles.css
        ├── index.js
        ├── howdy-world
        │   ├── index.html
        │   ├── styles.css
        │   └── index.js
        └── second-post
            ├── index.html
            ├── styles.css
            └── index.js

This will look very familiar to anyone who's worked on the internet for a very long time, but it's cool because this keeps your website's infrastructure simple and easily deliverable across whatever server or CDN you choose. But again, this is the Gatsby flavor of React.

In a more "traditional" (i.e., Create React App-style) React app, the public directory serves a little bit different of a purpose. With Create React App, you'll get something like the following:

react-app (Create React App example)
└── public
    ├── index.html
    ├── favicon.ico
    ├── logo192.png
    ├── logo512.png
    ├── manifest.json
    └── robots.txt

Out of the box you'll get a favicon.ico, two logo .png files, and a manifest.json; the latter three come into play if you make your application into a Progressive Web App. You'll also get a robots.txt file, which is a static file used for SEO purposes; it tells search engine crawlers which pages to index and which ones not to index. Lastly, Create React App drops the largely empty index.html file into the public directory.

index.html

I discussed the mostly empty index.html file in my previous blog post, but this is an HTML file that looks something like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

There's not a lot going on here. The favicon and logo files are loaded within the <head> tags using a %PUBLIC_URL% placeholder. This placeholder gets replaced with the actual endpoint of the public directory when the application is built and served.

Further down in the file, within the <body> tags you'll see this:

<noscript>You need to enable JavaScript to run this app.</noscript>

This will display if a user has JavaScript disabled on their browser. Obviously, we need JavaScript to run a JavaScript-based library. But then beneath that is:

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

This should look familiar by now; it's where the actual React application will be placed.

This HTML file serves as the sort of root template of your web application, and the public directory serves as the root directory. If you need to load fonts or Google Analytics through a series of <script> tags, then those tags can be placed in the index.html file. If you have a font directory that you want access to, you can drop that into the public directory.

What's missing here (that we saw in the last blog post) are the react and react-dom library <script> tags. These are omitted here because Webpack will include them when you run the development or build scripts. When you run these scripts, Webpack will also keep an eye on changes that you make in the src directory, which is where you'll actually create and manage your React application files.

src Directory

Where in our last blog post we created an index.js file in the same directory as index.html and built our React App in there, for a more industry-standard approach, index.js and all of your component, logic, and style files will live in a src directory. Other than making sure certain static assets or global scripts are available to index.html, you don't really spend much time working in the public directory. Instead, the src directory is where you'll do the vast majority of your React development.

The contents of the src directory will vary greatly from project to project and team to team. Some projects will have a pretty extensive components directory within src, or globally shared utility functions may be located in a utils directory. If you're working in Gatsby or Next.js, you'll be able to take advantage of a directory-based routing system, so you'll probably encounter a pages directory that will correspond to the pages produced by that application.

I obviously cannot cover every different permutation of React application patterns, but there are two files that are found in just about every React application's src directory, and they generally serve consistent purposes from project to project, so I'll cover them here. These files are index.js and App.js.

index.js

Typically there will only be one spot in the entire codebase that calls the createRoot and render methods from react-dom. This is generally in index.js. The index.js file is usually the first bit of code that gets executed in your React app. It's responsible for rendering the React application and turning the React elements we write into live DOM nodes.

It's also common to do "setup" tasks in index.js. Apps made with Create React App, for example, will have some code to monitor the performance of the app. You will also generally include a global CSS file here.

We don't typically want to render a ton of JSX in index.js since it's mostly there to set up the app. Instead, it usually only renders a single React element: <App />. This React element gets imported from App.js. Here is an example of a basic index.js file in action:

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

App.js

It's common for React projects to have a component called App, short for "Application." This is the "home base" React component of the project, the component that is an ancestor to every other component.

App will typically manage "core" layout stuff, like headers and footers. If you're using something like React Router, App will often also include the top-level routes.

Here is a simplified example of a standard App.js file:

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>Howdy React</h1>
      </header>
      <main className="App-main">
        <p>This is a React application</p>
      </main>
      <footer className="App-footer">
        <p>Farewell!</p>
      </footer>
    </div>
  );
}

export default App;

This is all fairly straightforward, App.js defines a function, App(), that returns a <div> that contains a <header>, a <main>, and a <footer>, all with a little bit of markup. Finally it exports the App function, making it available for index.js to use.

Components

So far we've referenced React components, but let's dig into that concept now.

In traditional HTML-based web development, you may have a portion of your webpage that looks something like this:

<div>
  <h2>Recipe table of contents</h2>
  <ol>
    <li>Introduction</li>
    <li>Ingredients</li>
    <li>Directions</li>
    <li>Recipe modifications</li>
  </ol>
</div>

This sort of thing could be embedded into your React app and it would work just fine, but it's not really reusable. If we had a lot of pages that each had a table of contents that was slightly different, we would want to be able to minimize how often we repeat our code. That is where a React component comes in.

In React, a component is a reusable UI element for your app. A component can be as small as a single element, or it can be the entire application, whose contents also includes other components. The App() function in our App.js example above demonstrates the latter, but let's start simple.

Say we want to make a reusable TableOfContents component. Let's start by defining it within a TableOfContents.js file:

// TableOfContents.js
function TableOfContents() {
  return (
    <div>
      <h2>Recipe table of contents</h2>
      <ol>
        <li>Introduction</li>
        <li>Ingredients</li>
        <li>Directions</li>
        <li>Recipe modifications</li>
      </ol>
    </div>
  );
}

export default TableOfContents;

Very cool, now we can import our new TableOfContents component into our App and use it like so:

// App.js
import TableOfContents from "./TableOfContents";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>Howdy React</h1>
      </header>
      <main className="App-main">
        <p>This is a React application</p>
        <TableOfContents />
      </main>
      <footer className="App-footer">
        <p>Farewell!</p>
      </footer>
    </div>
  );
}

export default App;

We simply import it using the exported name, TableOfContents, and then render it within our JSX markup like any other self-closing element: <TableOfContents />

In fact, we could even drop a bunch of <TableOfContents /> components in if we wanted:

// App.js
import TableOfContents from "./TableOfContents";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>Howdy React</h1>
      </header>
      <main className="App-main">
        <p>This is a React application</p>
        <TableOfContents />
        <TableOfContents />
        <TableOfContents />
        <TableOfContents />
        <TableOfContents />
      </main>
      <footer className="App-footer">
        <p>Farewell!</p>
      </footer>
    </div>
  );
}

export default App;

This would produce the same markup five times in a row. But that would be a silly thing to do.

Two quick notes:

  1. We are leveraging ES6 modules to import and export these components. That's super nifty! I won't be diving into ES6 modules because that is an extensive topic on its own.
  2. You'll notice that App.js and the App() function it exports as well as TableOfContents.js and the TableOfContents() function that it exports are capitalized. This is necessary for React, because it makes sure that React knows that our intention is to pass a custom component as the argument to the transpiled React.createElement() function call, not a built-in component like <div> or <span>. Read more about that here.

A Brief Intro to Props and Iteration

So we've separated our table of contents into its own TableOfContents component, what's the big deal? Well as it currently stands, it's still static, and each time it gets imported it produces the exact same code. However, we can make the contents of the component dynamic by passing it props. Props look sort of like HTML attributes when they get called, but they function kind of like arguments that get passed to your component.

Say we want to change the text contents of the <h2> element. We could start to do so updating our TableOfContents.js file like so:

// TableOfContents.js
function TableOfContents({ title }) {
  return (
    <div>
      <h2>{title}</h2>
      <ol>
        <li>Introduction</li>
        <li>Ingredients</li>
        <li>Directions</li>
        <li>Recipe modifications</li>
      </ol>
    </div>
  );
}

export default TableOfContents;

Now when we call the component, we can pass the actual value we want to use in place of {heading} like so:

// App.js
import TableOfContents from "./TableOfContents";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>Howdy React</h1>
      </header>
      <main className="App-main">
        <p>This is a React application</p>
        <TableOfContents title="Cookie recipe table of contents" />
      </main>
      <footer className="App-footer">
        <p>Farewell!</p>
      </footer>
    </div>
  );
}

export default App;

Here is what the same thing looks like, transpiled to plain JavaScript:

React.createElement(
  TableOfContents,
  {
    title: "Cookie recipe table of contents",
  }
);

We can take this a step further and pass an array of items that will get used for the list within the table of contents. Let's update App.js to put our table of contents list items into an array, and pass that array as a prop to the <TableOfContents /> component:

// App.js
import TableOfContents from "./TableOfContents";

function App() {
  const tableOfContents = [
    "Introduction",
    "Ingredients",
    "Directions",
    "Recipe modifications",
  ];
  return (
    <div className="App">
      <header className="App-header">
        <h1>Howdy React</h1>
      </header>
      <main className="App-main">
        <p>This is a React application</p>
        <TableOfContents
          title="Cookie recipe table of contents"
          list={tableOfContents}
        />
      </main>
      <footer className="App-footer">
        <p>Farewell!</p>
      </footer>
    </div>
  );
}

export default App;

Now we need to update the function in TableOfContents.js to accept that array, loop over it, and render each array item within <li> tags:

// TableOfContents.js
function TableOfContents({ title, list }) {
  return (
    <div>
      <h2>{title}</h2>
      <ol>
        {list.map(item => <li>{item}</li>)}
      </ol>
    </div>
  );
}

export default TableOfContents;

So now we have a much more modular component. We could pass any text or array that we wanted to use as the heading text and table of contents list items. This isn't totally production ready just yet though. I'll be doing a more thorough exploration of props and iteration in a future blog post, so stay tuned.

Fragments

The last thing I want to touch on in this blog post is the React fragments. By default, React only allows for a single element to be returned by a component.

This won't work:

import React from "react";

function App() {
  return (
    <h1>Welcome to my homepage!</h1>
    <p>Don't forget to sign the guestbook!</p>
  );
}

export default App;

That's because in JavaScript, a function can only return a single item, not two or more. In essence, this transpiles to something like:

React.createElement(
  "h1",
  {},
  "Welcome to my homepage!",
  "p",
  {},
  "Don't forget to sign the guestbook!",
)

...and that just doesn't work.

One option (and the way it used to be done) is to wrap all of the elements you want to return in a <div>:

import React from "react";

function App() {
  return (
    <div>
      <h1>Welcome to my homepage!</h1>
      <p>Don't forget to sign the guestbook!</p>
    </div>
  );
}

export default App;

...but this means we wind up with a lot of extra <div>s, which makes our markup messy, brittle, and prone to accessibility issues.

React.Fragment

Instead, we can now use fragments. A fragment is a special React component that doesn't produce a DOM node:

import React from "react";

function App() {
  return (
    <React.Fragment>
      <h1>Welcome to my homepage!</h1>
      <p>Don't forget to sign the guestbook!</p>
    </React.Fragment>
  );
}

export default App;

This ultimately produces the something like the following HTML:

<body>
  <div id="root">
    <h1>Welcome to my homepage!</h1>
    <p>Don't forget to sign the guestbook!</p>
  </div>
</body>

<></> Shorthand

There is also a nifty little shorthand available with <></>:

function App() {
  return (
    <>
      <h1>Welcome to my homepage!</h1>
      <p>Don't forget to sign the guestbook!</p>
    </>
  );
}

export default App;

Both the verbose and the shorthand version produce the following JavaScript:

React.createElement(
  React.Fragment,
  {},
  /* Children here */
);

Review

We covered a lot of ground in this blog post!

  • The entry point to your React app is package.json. This will define dependencies (like react and react-dom), and scripts to actually run the app
  • The public directory is where public-facing, static assets live that the final application will use, but that don't need to be generated by Webpack
    • The index.html file where your React app actually gets embedded lives in the public directory. You can import <scripts> that your application will use in this file (like Google Analytics or font loading scripts)
  • The src directory is where your actual React application lives and is developed; this directory will contain all of your components, logic, and style files
    • The index.js file that calls the createRoot and render methods lives in the src directory. This is usually the first bit of code that gets called in your React app, and it typically handles setup tasks and calls the <App /> component
    • The App.js file also lives in the src directory. This file contains the <App /> component, which is the ancestor to all other components in your application. App typically manages core layout stuff, like headers and footers, and in some cases also handles routing
  • A component is a reusable UI element for your app; you can make it reusable with different pieces of data and content by passing it props. On a syntax level, a component is essentially a JavaScript function that returns DOM nodes
    • By default, React only allows for a single element to be returned by a component. If we want a component to render two or more adjacent elements, we can use fragments.
    • A fragment is a bit of React syntax that allows multiple child nodes to be returned in a place where only one item can typically be returned
      • You can either use the verbose <React.Fragment></React.Fragment> syntax or the shorthand <></> syntax