Write React like a pro. React Icon

Follow the ultimate React roadmap.

Getting started with React and TypeScript

Among the heavy hitters in the frontend web application frameworks, React, by the lovely engineers at Facebook, is easily one of the most popular. According to the React docs, React is:

A javascript library for building User Interfaces.

React gives us a powerful API for build component-based, declarative, UI elements. We can then compose these components together to make feature-rich and powerful web applications. The key here is the component-based architecture. Components are the building blocks of React applications and they should encapsulate a single piece of UI functionality as well as maintain their own state. Input data can be passed into a component via the component props. The component can interact with these props and display them through the component render to be displayed to the user in the UI.

TypeScript

To make our components resilient, and better understand the data props being passed into them, we can add TypeScript support to our React applications and type our props. By adding this support, our app can use the power of TypeScript to provide intellisense, linting, and compile-time errors; these work together to help engineers build and compose components. Doing this, if we try to pass in incorrect data to a component, our app will fail to compile vs. failing at runtime. This means these failures should not reach the end-user.

But what is TypeScript? Created by Microsoft, according to the docs:

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript

TypeScript works on any browser, on any OS, and can be used to build scalable, powerful NodeJs APIs as well.

Let’s Build an App

Time to put these tools to use. We will build a React application with TypeScript support to display and interact with some data and see the value of building our React components using `TypeScript.

Create the App

First, lets bootstrap our app using the create-react-app script:

npx create-react-app my-dive-log --typescript
# or
yarn create react-app my-dive-log  --typescript

Note: per the create-react-app docs, they recommend uninstalling the create-react-app if you have it installed globally and using one of the methods listed above to guarantee you are using the most up-to-date version.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

Once finished, we have a directory: my-dive-log with a few files generated by the script to get us started:

|- node_modules
|- public
| |- favicon.ico
| |- index.html       # <- root app page where the compiled JavaScript and css are injected to run the app
| |- manifest.json    # <- used for PWA
|- src                # <- app code root directory
| |- App.tsx          # <- Describes our Application JSX. Top-Level component
| |- App.css          # <- App component styles source
| |- App.test.tsx     # <- App.tsx unit test file
| |- index.tsx        # <- application root file where the App is registered with the ReactDOM into the root element in the index.html
| |- index.css        # <- root app level styles
| |- serviceWorker.ts # <- PWA service worker code
|- .gitignore
|- package.json
|- tsconfig.json      # <- TypeScript configuration file. Specifies the root files and compiler options

This provides us with the required packages and files to establish our React app as a TypeScript project so it can be compiled and ran. If you look at the scripts in the package.json file, you will see a few scripts like start. This uses the react-scripts start while will compile and run our application. Open a terminal in this directory and let us run it:

yarn start
# or, with npm
npm start

If successful, a browser window should open with our app!

TypeScript Linting

The addition of TypeScript help to enforce not only type-safety but allows us to introduce rules to make our code more readable, maintainable, and functionality-error free. These rules are established through a static analysis tool calling tslint. It allows us to adopt industry-standard linting and gives us the ability to override rules as you see fit (want longer print lines? single-quotes vs double quotes? etc). Let’s add tslint and a couple tslint rules libraries to our app to enforce quality coding practices:

yarn add --dev tslint tslint-config-prettier tslint-react
# with npm
npm install --save-dev tslint tslint-config-prettier tslint-react

To add support, create a file in the root directory called tslint.json; this is where we establish and can override our tslint rules:

{
  extends: ['tslint-react', 'tslint-config-prettier'],
  linterOptions: {
    exclude: [
      'config/**/*.js',
      'node_modules/**/*.ts',
      'coverage/lcov-report/*.js',
    ],
  },
  rules: {
    'jsx-no-lambda': false,
  },
  defaultSeverity: 'warning',
}

Pretty basic right now, we extend the tslint-react & tslint-config-prettier plugin libraries to utilize the rules they have established with 1 override in the rules section: "jsx-no-lambda": false; this is just a personal preference to turn off the jsx-no-lambda rule.

A look at our App React Functional Component

As mentioned, one of the files generated for us is ./src/App.tsx. Open this file and take a look:

import React from 'react';
import logo from './logo.svg';
import './App.css';

const App: React.FC = () => {
  return (
    <main className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </main>
  );
};

export default App;

The key here is that we have a const variable named App which is of type React.FC or Functional Component. React promotes 2 types of components: Class Components and Functional Components. The idea of a Functional Component is that it is a pure function; pure meaning - its return value is only determined by the input values (it does not use-values outside of those passed to it as input), its return values are always the same given the same input. Pure functions are the way to be as they are much easier to encapsulate, test, maintain, and read. By design, functional components are stateless; meaning they do not have access to state or have a lifcecyle; this was until the addition of hooks in React 16.8. Now with hooks, we can use React Functional Components that interact with our state as well. Hooks are amazing and I will be writing another post about hooks in this article series; stay tuned.

React works on the component concept that allows us to build components and then compose them together. Our App component defined here will be our top-level component where we will compose our other components as children. I like to think of this as our App shell where we can define components that will be shared across our app; things such as our app header, with navigation, authenticated user info, etc; an app footer if needed, our top-level route definitions if needed; etc.

We will build a couple of components in this article and add them to this App component.

App Header

To start, let’s create a header component that will sit at the top of our app; show the app title, a short description, and a couple of navigation links.

Create a folder inside of ./src named containers; this is where we will place smart “containing” components.

mkdir ./src/containers

And inside of this directory, create a file named AppHeader.tsx; note the .tsx extension, this declares our file as a JSX file with TypeScript. Using this extension allows us to have type-safety with our components and reap the advantages of TypeScript.

touch ./src/containers/AppHeader.tsx
# create the css file as well
touch ./src/containers/AppHeader.css

Open the created AppHeader.tsx file and let’s create a React Functional Component that takes in props for use in our header.

import React from 'react';

import './AppHeader.css';
import AppHeaderNavLink, { NavLink } from './AppHeaderNavLink';

// define our AppHeader properties that will be passed into the component
export type AppHeaderProps = {
  title: string;
  description: string;
  links: NavLink[];
};

// create React Functional Component variable that will render our code for our header.
// React.FC takes a type of the props that can be passed into the component
const AppHeader: React.FC<AppHeaderProps> = React.memo(
  ({ title, description, links }) => {
    return (
      <header className="app-header">
        <section className="app-title">
          <h1>{title}</h1>
          <small>{description}</small>
        </section>
        <span className="fill-space" />
        <section className="app-links">
          {links &&
            links.map((link: NavLink) => (
              <AppHeaderNavLink
                label={link.label}
                route={link.route}
                key={link.label}
              />
            ))}
        </section>
      </header>
    );
  }
);

export default AppHeader;

First, we define a type; AppHeaderProps that represents our props. This type will have the app title, description, and an array of links of type NavLink (described next). This type will be declared as the type on our AppHeader Functional Component. This is what the component will expect as props. If a value for a prop is not provided, we will see the little red-squiggly line under our component declaration telling us that it is missing the given type. Same if we give a prop an incorrect value type. This is the magic of adding TypeScript to our React components. It helps us better understand what our components expect and will fail at compile time, instead of runtime. This also helps new engineers to a codebase pickup and work in the code quicker as the tooling helps them learn and understand the component and how to compose them together to build out the app.

In the AppHeader component you can see we bring in another component as well: AppHeaderNavLink, we will go through this component now. It is a simple component that takes and displays a NavLink so that we can add routing to our component; it is a good show of the composability of React and the use of array.map to iterate over an array and compose components. Note This will not route anywhere just yet, there will be an article in this series on react routing as well.

We will create this component, along with its styling in a components directory. This directory will contain low-level view components (sometimes called dumb components). These components should not be responsible for anything more than receiving and displaying its received props. Any business logic should be done in a container component. Create the components directory and this AppHeaderNavLink.tsx file in the directory:

# create directory
mkdir ./src/components
# create AppHeaderNavLink files
touch ./src/components/AppHeaderNavLink.tsx
touch ./src/components/AppHeaderNavLink.css

And open the created AppHeaderNavLink.tsx:

import React from 'react';

import './AppHeaderNavLink.css';

// define a Navigation Link type for our links
export type NavLink = {
  label: string;
  route: string;
};

const AppHeaderNavLink: React.FC<NavLink> = React.memo(({ label }) => (
  <span className="nav-link">{label}</span>
));

export default AppHeaderNavLink;

Just like the AppHeader component above, we define a const variable of type React.FC, or functional component, that takes NavLink for its prop types. We did introduce another React concept here: React.memo. This function was introduced with React 16.6. memo is short for memoization. This is a concept to speed up the rendering of a component by memoizing the rendered result. What this does is before the next render of the component, if the props passed into the component are the same from the previous render, the memoized result will be reused; saving time. As with anything, there are tradeoffs and React.memo should not be used in all situations (still no silver bullets). Check out this article on good use-cases on when to use React memoization here.

Adding the AppHeader to App

With our AppHeader component built, we need to add it to our App.tsx file to display the header:

First, we need to import the AppHeader, and we will also import the AppHeaderProps to make an instance of these props to pass to the component:

import AppHeader, { AppHeaderProps } from './containers/AppHeader';

// build an instance of our AppHeaderProps to pass to the AppHeader component
const headerProps: AppHeaderProps = {
  title: 'My Dive Log',
  description: 'Log, Track, Review your dive logs and relive the experience',
  links: [
    {
      label: 'Logs',
      route: '/logs/list',
    },
    {
      label: 'New Entry',
      route: '/logs/create',
    },
  ],
};

We will now add this component to the App render as a child component:

const App: React.FC = () => (
  <main className="App">
    <AppHeader
      title={headerProps.title}
      description={headerProps.description}
      links={headerProps.links}
    />
  </main>
);

I replaced all the content inside of the top-level main element and added the header here as the first child in the tree. Just like adding the AppHeaderNavLink component to our AppHeader, we compose the AppHeader into App. This component-based design is the crux of the React framework. Build encapsulated, readable, single-use components to build UI functionality and then compose these components together to build an app.

App Body

With our header in place, let’s give some content to the body of the app to finish out this first iteration. Create another container component in our containers directory called AppBody (I know, super great name).

# create AppBody tsx file
touch ./src/containers/AppBody.tsx
# and create AppBody css styles
touch ./src/containers/AppBody.css

Let’s add some basic props to display a header, a quote, and some children in this area.

import React from 'react';

const AppBody: React.FC<{ header: string; quote?: string }> = ({
  header,
  quote,
  children,
}) => (
  <section className="app-body">
    <section className="body-content">
      <h2>{header}</h2>
      {quote && <blockquote>{quote}</blockquote>}
    </section>
    {children && { children }}
  </section>
);

export default AppBody;

Unlike in our AppHeader, I did not create a type for the props and built it out inline. This works just fine if we have a limited number of props for a component that we will not reuse in other areas of the app. Our AppBody also exposes another react concept you may be familiar with; children. children is Reacts nomenclature of child elements that can be passed into the component and displayed. These elements can be other html elements like a div or span, etc; or it can be components that we have created. This is a way for a component to declare that whoever utilizes this component can pass in any children as they wish and they will be rendered into the DOM where this component places them. This is a very powerful method of composing components in our app. You also see a way to test if a prop has a value before adding an element for the prop into the DOM:

{
  quote && <blockquote>{quote}</blockquote>;
}

This is how we can add/remove elements to/from the DOM. This says if the prop quote is not null or undefined, then add the blockquote element to the DOM to display the quote.

Open App again and lets import and add the AppBody to our app and pass some children in. First, we need to import the AppBody:

import AppBody from './containers/AppBody';

And now we will compose it into our component. I generated some sweet bacon ipsum text for our quote to pass into the body:

const bodyQuote = `
  Spicy jalapeno bacon ipsum dolor amet ball tip turducken brisket veniam beef ribs ipsum, ex pig doner strip steak t-bone.
  Bacon swine shankle, pastrami tail chuck strip steak kevin.
  T-bone mollit kevin chicken id sirloin tenderloin irure pork chop ball tip lorem qui.
  Tenderloin et tri-tip, porchetta cillum in occaecat. Cow sint magna pork loin, officia laboris in boudin doner.
  Frankfurter burgdoggen cupim, pariatur consequat salami tempor.

  Bresaola jerky laboris alcatra shoulder filet mignon exercitation proident non. Leberkas hamburger aute labore meatball.
  Shank labore reprehenderit culpa. Buffalo eu shankle chuck sed cillum ut burgdoggen turducken bresaola pariatur landjaeger.
  Consectetur excepteur burgdoggen filet mignon enim, boudin ad pork chop. Turducken ut sint, cow pork chop dolore chicken reprehenderit jowl.
  Ad pariatur pig fatback.
`;

const App: React.FC = () => (
  <main className="App">
    <AppHeader
      title={headerProps.title}
      description={headerProps.description}
      links={headerProps.links}
    />
    <AppBody header="My Dive Log Dashboard" quote={bodyQuote}>
      <p>
        I am a child element to the App Body and will be rendered in the App
        Body
      </p>
    </AppBody>
  </main>
);

There we go. Now we have some body content in our app that had a header, some tasty meat quotes and as I mentioned, we passed in a child element p basic HTML element to the AppBody as well and it is displayed where we placed our children in the AppBody. We can add as many, or none, children in here as we want and they will all be added to the DOM.

Conclusion

With our App built out, we can open a terminal in the root directory and start the app and we should see the AppHeader and below it our AppBody:

yarn start
# or with NPM
npm stat

React is a great framework for building very powerful apps working on this same concept of component-based design. This scales very well as we can add more and more components to use as building blocks to build out our app. This first article goes over this component concept as well as how to use TypeScript in conjunction with React to create type-safe, readable, maintainable components. The next article in this series will go over the concept of Hooks as we will start to make our components a little smarter and able to manage their state. Stay tuned for more to come and I hope you enjoyed this article.

Learn React the right way.

The most complete guide to learning React ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover