Building a React Application: Part I

Eric Gibby
Imagine Learning Engineering
18 min readJul 13, 2020

React is a powerful JavaScript library whose primary focus is on rendering fast, performant UIs, using one-way data-binding to populate the view. That might just sound like a bunch of technical buzzwords because, well, it is. The main takeaway from all those buzzwords is that React is intended for building UIs. In order to build a large application, you’ll likely need more than just React. Below is a diagram of how a typical React application might be structured. As you can see, React is really only the leftmost part of the diagram.

Anatomy of a React Application

That being said, there’s still a whole lot that can be built with just React. And the flexibility that comes with React can make it challenging to get it right.

In this tutorial, we’re going to lay the ground work for building a basic React application. We’ll cover some core concepts, like bootstrapping your application with Create React App, building components, managing state using React Hooks, and configuring routing with React Router.

To fully understand this tutorial, you’re going to need some familiarity with JavaScript. We’re actually going to write our application code in TypeScript, because TypeScript is awesome! But if you’ve never written any TypeScript code before, don’t let that scare you off. TypeScript is often referred to as syntactic sugar for JavaScript. It’s actually a superset of JavaScript which compiles down to plain JavaScript (so plain JavaScript is technically valid TypeScript, but TypeScript is not necessarily valid JavaScript). This MSDN article, Understanding TypeScript — although a little dated — gives a good overview of TypeScript, particularly for C# and JavaScript developers.

You’ll also want have a minimal level of comfort working with command line tools. In this particular case, using dependency management tools for Node.js, like npm or Yarn.

System Requirements

To build this application, you’re going to need a recent version of Node.js installed. You can download the installer directly from their website. However, I prefer using a tool like Node Version Manager (nvm) which allows you to install different versions of Node in parallel and switch between them. There are versions of nvm for both MacOS (or Linux) and Windows.

Application Requirements

Before we can build an application, we need to understand what it is we’re building. For this tutorial, we’re going to build a game called Spider. For those who think they’ve never played this game, it’s really just Hangman, but less inappropriate since you draw a spider hanging from its web rather than a human.

Our requirements are as follows:

  • A person playing the game can enter a word or phrase to be guessed
  • Text cannot be seen as it’s typed
  • Letters of the word/phrase to guess are displayed as blanks (underscores) until guessed
  • Other characters in the word/phrase (e.g. spaces, apostrophes, etc.) are displayed
  • Letters to guess (all letters of the alphabet) are displayed on screen as buttons
  • Clicking a correct letter disables the button and displays the letter in the word/phrase
  • Clicking an incorrect letter disables the button and displays incorrect indicator
  • Structure from which the spider will hang is displayed on screen
  • With each incorrect guess another part of the body is filled in: abdomen, cephalothorax, individual legs
  • Game can be reset at any point

The completed project is available in this GitHub repository, with branches you can check out for the various steps of the tutorial. I’d recommend writing the code yourself though, so you have a chance to get more familiar with it. You can also view a working version of the final application at https://ericgibby.github.io/spider-app.

Getting Started

For this project we’re going to use Create React App (CRA) to bootstrap our application. As stated on their webpage:

Create React App is an officially supported way to create single-page React applications. It offers a modern build setup with no configuration.

We’re going to use it because it’s quick, it’s easy, and it comes directly from the React team.

We’re also going to use Tailwind CSS for styling our application. Tailwind CSS is a utility-first CSS framework, which gives us a lot of flexibility in how we build and style our application. If that doesn’t mean anything to you, that’s OK. You can read more about using CSS utility classes in Adam Wathan’s article, CSS Utility Classes and “Separation of Concerns”.

To get started, let’s head to the terminal or command prompt and run the following command:

npx create-react-app spider-app --template craco-tailwindcss-typescript

Let’s break down this command:

  • npx is a package runner, which allows us to run a package binary without having to install it first.
  • create-react-app is the package we’re running. It will do all the heavy lifting of stubbing out our project, configuring dependencies, and setting up our build tooling.
  • spider-app is the name of our application. CRA will create a folder with this name in the current directory, and place all the project files inside that directory.
  • --template tells CRA what project template to use when creating this project. CRA ships with a couple of built-in templates: the default template which uses JavaScript, and the typescript template which uses TypeScript. For this project, since we’re using Tailwind CSS, there is some additional configuration required to get that set up. Rather than going through that here, I’ve created a custom template which handles that configuration for us with the craco-tailwindcss-typescript template.

At this point, you should have a fully functional React application. You can run these commands to fire up the development server, open your browser, and load the application:

cd spider-app
yarn start

Now let’s take a quick look at our project structure (for more information, refer to the documentation). Inside the spider-app directory, you’ll find a bunch of configuration files which you can — for the most part — just ignore. Inside the public directory you’ll find the index.html file, which is the document that loads in the browser when a user navigates to the application. In the src directory, you’ll find our TypeScript source files. index.tsx is the entry point for the application. And this part inside index.tsx is where the magic starts:

This is telling React to render the App component inside the DOM element with id="root". So basically, everything inside that root element is going to be controlled by React.

So now that we have a working React application, let’s get into the code!

Components and JSX

React is a component-based library for creating UIs. It allows you to build encapsulated components that manage their own state, then compose them to make more complex UIs. So a React application is really just a tree of individual React components.

Components can be written in React as either classes or functions. For this tutorial we will be writing function components. Function components are more concise, and are less prone to common pitfalls like storing internal state in instance variables, using inheritance for code reuse, or putting business logic in class methods. If you want to learn more about class components, refer to the documentation on the React website.

Function components are just what they sound like: a function. A function component takes a single argument, which is an object representing the properties or “props” for the component. Props are just data being passed to the component. The component is then in charge of displaying that data in some way, or passing it down to other components. In the end, your function returns React elements describing what should appear on the screen.

If you open up App.tsx in your IDE, you’ll notice the file contains HTML markup inline with the TypeScript code.

Note that it is not a template string containing markup, but rather the markup is part of the TypeScript code. This is known as JSX (XML in JavaScript), which is a syntax extension to JavaScript. While not required for writing React code, it is recommended and makes life a lot easier.

Let’s write some JSX by deleting the sample code in App.tsx and replacing it with a simple heading for our game:

We can also get rid of some of the the other sample content files, such as the logo.svg file and App.css since we no longer need any custom styles for our app component.

Notice that as you make changes and save your files, your browser will automatically load the updated content. This is thanks to react-scripts, which was configured by CRA and uses the webpack’s DevServer under the hood.

Our First Component

Now that we have our application shell, we can start to create the various building blocks, or components, that will make up our application. Let’s start with our first requirement: “A person playing the game can enter a word or phrase to be guessed.”

For this, we’re going to need a form with a text input and a submit button. We’ll take an atomic design approach, and start with small, focused components that can be composed together. With that in mind, we’ll create a simple Button component to start.

React projects can be structured however you like. My preference is to create a components directory inside the src directory, and then place directories inside that for each new component. The individual component directories can contain the component source file and any other supporting files, such as CSS, tests, and utility functions specific to that component.

Create a new file at src/components/Button/Button.tsx. This component will take props for specifying the button type, the button text, and an onClick handler. We will also add some default styles to the button using Tailwind CSS’s utility classes.

Tailwind CSS is beyond the scope of this tutorial. As an additional exercise, feel free to play around with customizing the style of your button.

Notice the use of camelCase property names for HTML attributes like class and onclick. According to the React website:

Since JSX is closer to JavaScript than to HTML, React DOM uses camelCase property naming convention instead of HTML attribute names.

For example, class becomes className in JSX, and tabindex becomes tabIndex.

You may also notice the use of curly braces inside the JSX. Curly braces can contain any JavaScript expression. In the case of the onClick and type attributes on the button element, it is binding the values from theButton component’s props to the attributes on the element. In the case of {text} it is simply rendering the string passed in through the text prop inside the button element.

Now add your Button component to the App component and see your snazzy new button rendered on the page. You can even add an event handler function to the onClick prop of your button to see it in action. Notice how we can define an arrow function directly inside the curly braces, because curly braces can contain any JavaScript expression.

Form Controls

Now that we’ve mastered the basics of a component, let’s create something a little more complex: a text input component.

Create a new file at src/components/TextInput/TextInput.tsx. This component will take props for specifying the input type, the value, an optional label, and an onChange event handler.

In this component, we have some conditional content that only renders if a value is specified for the label prop. We’re able to use the inline conditional operator && because in React, if an expression evaluates to undefined, null, or false, then nothing is rendered.

Now you can plug your new TextInput component into the App component and see how it looks.

You may have noticed there are a couple of props we forgot to set on our TextInput component: onChange and value. Go ahead and set a value on the component:

<TextInput label="Enter a word or phrase" type="password" value="Hello world!" />

If you look in the console in your browser’s developer tools after making that change, you’ll notice React has given you a very helpful error:

Warning: Failed prop type: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.

This makes sense, because we’re telling the input field what its value should be, but we’re not providing a way for that value to ever change. By specifying the value through props, we’re putting React in control of managing the state of the input element. We need to add an onChange event handler so we can update the state of the input each time it changes. This is what is referred to in the React world as a “controlled component.” This also happens to be the recommended way to handle form elements, such as input, textarea, and select, in React. There can be use cases where “uncontrolled components” make more sense, but that’s outside the scope of this tutorial.

For now, go ahead and take the value prop off of your TextInput component. We’ll implement a real solution next.

Managing Component State

Up to this point, the components we have created are stateless — they simply take inputs through props and output events. Those types of components can be referred to as “presentational components,” because their only concern is to present the data they’re given from their props.

Several years ago, Dan Abramov wrote an article, Presentational and Container Components, in which he discusses a pattern of separating concerns by creating stateless presentational (or “dumb”) components, and stateful container (or “smart”) components. One is focused on displaying the data, while the other is focused on managing the data. While he states that his views have since evolved, and warns about being dogmatic in following the pattern, it can still be a useful pattern to understand and be mindful of as you build your React applications.

Let’s make our first container component, a TextInputForm component that can pull our TextInput and Button components together. Create a new file at src/components/TextInputForm/TextInputForm.tsx. This component will take a single onSubmit prop, which is a handler that receives the value from the form. We also want to set the type prop on our TextInput to password to meet our second requirement: “Text cannot be seen as it’s typed.”

Then drop our new TextInputForm into our App component so you can see it in action.

So far, this is nothing new. But you’ll notice the handleSubmit handler function on the form element isn’t actually passing any of the form values to the onSubmit function. So how do we get the values from our TextInput? Well, that part’s easy. We can wire up our onChange handler on the TextInput component.

So now we have the value. But how do we hang onto it? Remember, your component is a function that gets called any time React detects a change. This is known as re-rendering, and it happens a lot because React can do it very quickly and efficiently. But how do you get a value to stick around between renders? Storing it in a variable inside the component function won’t last between renders. Storing it in a variable outside the function makes the value global to all instances of the component, which isn’t very reusable. Enter React Hooks.

According to the React website: “Hooks are functions that let you ‘hook into’ React state and lifecycle features from function components.” There are a number of extremely useful hooks that ship with React. The useState hook provides a means for managing internal component state. It allows you to store a value that persists across renders, and plays nice with React’s change detection cycle (meaning, your component is aware of when the value changes and will update accordingly). For our particular problem, it will give us exactly what we need. Calling useState returns an array that contains the currently stored value, as well as a function for updating the stored value. With that in place, we can properly hook up our onChange handler and value prop on the TextInput component, as well as properly pass the current value to the onSubmit function.

As an additional exercise, try adding a toggle to show/hide the text in the TextInput.

There’s a lot more to hooks that we won’t cover here. I highly recommend reading the hooks documentation. It’s also worthwhile to understand the rules of hooks as outlined by the React team. To be clear, hooks aren’t the only solution. But they’re tailored toward function components, which fits our project nicely. If you’re interested in solutions for class components, you can read more in the documentation.

If we refer back to the diagram at the beginning of the tutorial, we’re starting to get a clearer picture of how the various pieces work together.

Container Component

We’ve got our TextInputForm component, which serves as our container, managing state and passing it down to our presentational components: TextInput and Button. Our presentational component can then fire off events which get handled in our container, where it can update the state accordingly.

Routing

OK. We’ve got a form that allows us to enter in a word or phrase to be guessed. But what happens after the user submits the form? Let’s navigate to a new page where the user can now guess the word/phrase.

Routing in a single-page application can get complicated if you’re trying to manage it all on your own: keeping track of URL changes, parsing parameters from the path and query string, using the browser’s History API, blah, blah, blah. Fortunately, React Router makes routing dead simple. It provides a set of components for declarative routing, and custom hooks for interacting with the History API.

Add React Router to your application with this command:

yarn add react-router-dom# Add types for better TypeScript supportyarn add --dev @types/react-router-dom

Now let’s add some routing. First, we’ll need to wrap anything that needs access to routing inside the BrowserRouter component. We’ll do that in our index.tsx file. Then we’ll add some Route components to our App component. Each route component renders its contents if the URL matches the path specified in its path prop. We’ll have our text entry form live at /start.

We’ll also want a catch all route (path="*"), so if a user enters an invalid URL we can redirect them back to the /start route. We can do that with the Redirect component. But we’ll only want our catch all route to render if no other routes are matched. Otherwise, we’ll end up with an infinite redirect — which is not a great user experience. Fortunately, React Router also provides a Switch component, which works similarly to a switch statement in code. The Switch component renders the first Route or Redirect component that matches the location, so we’ll only hit our redirect if the location doesn’t match any of our other routes.

index.tsx
App.tsx

This is pretty good so far. But for organizational purposes, I prefer to create container components for each route. Create a file at src/containers/StartContainer.tsx. This component will contain everything that renders with the /start route.

You may have noticed this component uses a weird, empty tag (<>…</>) to wrap the contents. This is a shorthand syntax for the React.Fragment component. Since a React component can only return one top-level element, React.Fragment allows you to wrap multiple elements without adding extra elements to the DOM (in other words, fewer divs).

You may also have noticed this use of the spread syntax: <TextInputForm {...props} />. Remember, JSX represents actual objects in JavaScript/TypeScript, so this is a valid way to merge props without having to specify each and every prop individually.

Let’s also stub out a PlayContainer, so we’ll have a place to house our gameplay components. Create a new file at src/containers/PlayContainer.tsx.

Now that we have our StartContainer component, let’s update our App component to use it, and add a /play route that uses our PlayContainer component.

With multiple routes in place, we’re ready to add some navigation to our app. There are a couple of ways to add navigation with React Router: programmatically using the history object, or declaratively using the Link or NavLink components.

We’ll start by programmatically navigating from /start to /play after the user enters their word/phrase in the form. We can access the history object from React Router using the useHistory hook. Then it’s as simple as calling history.push('/play') in our form’s submit handler inside our App component.

Now let’s add a link in our PlayContainer to get back to the /start route.

And just like that, we’re able to navigate back and forth.

Building on What We’ve Started

So we’ve got a React application with some components, we’ve figured out how to manage state in our components, and we have routing set up. But we still can’t actually play the game. Let’s build out some more components for our gameplay.

Our next two requirements are:

  • Letters of the word/phrase to guess are displayed as blanks (underscores) until guessed
  • Other characters in the word/phrase (e.g. spaces, apostrophes, etc.) are displayed

We can create a component that handles that for us. Create a file at src/components/MaskedText/MaskedText.tsx. This component will take text (the word/phrase to guess) and usedLetters (an array of characters that have already been guessed) as props. It can then parse the text and determine which characters should be displayed and which should be replaced with underscores.

At first glance, there appears to be a lot going on in this component. You’ll notice we have a getCharacters function defined outside the component. This helper function does not rely on any state internal to the component, so there’s no reason to define it inside the component (where a new instance would be created every time the component renders). By extracting this logic into a separate function, we can also test that code independently from the UI.

Inside the component, we’re iterating over the words and letters to build up a collection of elements for displaying the text. Notice inside the map and reduce functions that each element we add to the collection includes a key prop. This is necessary to give the elements inside the array a stable identity, so React can identify which items have changed, are added, or are removed.

Another thing to point out in this component is that the getCharacters function is called each time the component renders — even if nothing has changed. While not egregious, this does have a performance impact. So how do we fix it? Hooks to the rescue!

The useMemo hook allows us to memoize expensive calculations during the render cycle. It takes a “create” function and an array of dependencies. Any time one of the dependencies changes, the function will execute again. Otherwise, it can return the cached value. So now it only calls getCharacters when text or usedLetters changes.

Now we can drop our MaskedText component in the PlayContainer, and add some state management to our App component.

PlayContainer.tsx
App.tsx

🎵 Just Keep Coding, Just Keep Coding! 🎵

Now we’re making some progress! So what’s next on our list of requirements?

  • Letters to guess (all letters of the alphabet) are displayed on screen as buttons
  • Clicking a correct letter disables the button and displays the letter in the word/phrase
  • Clicking an incorrect letter disables the button and displays incorrect indicator

Let’s create a LetterButtons component that displays a button for each letter of the alphabet. It can take text and usedLetters as props, just like our MaskedText component. And it can take an onClick handler that gives the letter that was clicked. Create a file at src/components/LetterButtons/LetterButtons.tsx.

Then add your LetterButtons component to the PlayContainer component. We can add an onClick handler and use the useState hook to keep track of letters that have been clicked.

You may have noticed this in our handleClick function:

setUsedLetters(previous => [...previous, letter]);

Rather than just passing in the new value, we gave it a function that takes the previous value as its argument and returns the new value. Keep in mind that setting state is an asynchronous action in React. Any time you need to update a value in state based on its previous value, you should follow this callback pattern to ensure it has the most current value when the state is actually updated.

It’s also worth mentioning that you should never mutate values stored in state. Rather than pushing the latest letter onto the usedLetters array, we used the spread syntax to create a new copy of the array and append the latest letter. This is because React’s change detection uses reference equality (===), so it may not re-render properly if the reference to the array doesn’t change.

Almost There!

Let’s see what’s left on our list of requirements:

  • Structure from which the spider will hang is displayed on screen
  • With each incorrect guess another part of the body is filled in: abdomen, cephalothorax, individual legs

We can create a Spider component that takes a step prop to indicate which image display. We’re just going to use a series of images that show more of the spider drawn with each image. They’re all the same size so we can swap in the appropriate image after each guess. You can download these SVG images, or feel free to create your own (I created mine on excalidraw.com). Just place them in the src/components/Spider directory, and create a Spider.tsx file along side them.

You can drop this into the PlayContainer component, but you’ll quickly notice that we’re going to need a count of incorrect guesses to know what to pass to the step prop of our Spider component. That’s easy enough to get, since it can be derived from our text prop and our usedLetters stored in state.

Because there are 10 different spider images, the game will be pretty easy. As an additional exercise try adding optional difficulty levels (e.g. one that adds the legs two at a time, or one that adds 4 legs at a time).

At this point we have a mostly working game. There are a few clean-up items we’ll want to take care of. First, let’s indicate when someone wins or loses, because that seems like a useful thing for a game to do.

And finally, let’s make sure we send the user to the /start route if they arrive at the /play route without a word/phrase already set. For this we can use another React hook, useEffect. This hook is intended for actions that cause side-effects while rendering the component — like redirecting away from the page.

It’s important to include the second argument in useEffect, the dependency array, because that’s how React determines if it needs to run that effect in the current render cycle. If none of the dependencies have changed, it won’t run.

We Did It!

Well, we did it. We created a React application with Create React App; built presentational components and container components; used React hooks to manage state, memoize expensive calculations, and perform side-effects; and handled routing with React Router.

It seems like a lot, but so far we’ve only covered the presentation layer of our application diagram.

Up next, in Building a React Application: Part II, we’ll explore some more advanced topics, like handling asynchronous calls to load data, managing global application state with Redux, and moving business logic out of components and into actions, reducers, and epics.

--

--