Skip to content

Getting Started with React and TypeScript

Getting Started with React and TypeScript

React and TypeScript makes for a fantastic combination when it comes to developing web sites and web applications. With React's focus on creating a fantastic user interface and TypeScript's ability to type check our code, it's never been easier to sanely develop a fantastic user experience.

Prerequisites

Before getting started, you'll need NodeJS and NPM installed. Go here to find instructions on how to install it on your computer.

You should also have a bit of knowledge in using React. If not, I highly recommend getting started learning React using vanilla JavaScript.

Create React App

Facebook's Create-React-App is a preview of the wonderful development experience React has to offer. It will enable us to start writing code immediately.

Check out their documentation here if you have any questions or want to dive in a touch deeper on how the tool works.

In summary, Create-React-App will set up a complete development environment for us. It will configure and conceal build tool configurations for us so that all we need to focus on is our code. Here is some of the tooling that CRA (create-react-app) sets up for us:

Let's use this tool now to create our TypeScript/React development environment. Use the following command to generate a new Typescript React App.

npx create-react-app my-app --template typescript

If you use yarn, use the following command:

yarn create react-app my-app --template typescript

After the tool has finished running, switch directories into my-app run the command npm start to spin up your development environment. Once it's done loading, we should see localhost:3000 open up.

Create React App Home Page

Folder Structure

Create-react-app scaffolds out the folder structure of your app for us, minimizing the amount of code you need to write to go from idea to production.

Here are some important files to take note of:

  • public/index.html - This is the HTML file that consumes the bundle generated by Create-React-App's hidden tooling.
  • src/index.tsx - This is the TypeScript entry point file. All of the project's dependencies are mapped out by Webpack (all set up for us invisibly by CRA) using this file. The bundle generated is injected into public/index.html. This file has a few responsibilities. The most important are:
    • import our main React component
    • Render that App component to the DOM using ReactDom.render()
  • src/App.tsx - This is our main React Component. The .tsx extension tells us that the file uses both TypeScript and JSX. This is the main component in our app which is rendered to the DOM by ReactDOM. We'll spend most of our time in this file getting the hang of writing React code using TypeScript.
  • tsconfig.json - This is the TypeScript configuration file. This file was generated for us by Create-React-App and we can freely edit this configuration to best suit our needs.

Hello, World - With TypeScript and React

Let's get to coding.

To keep things organized, make a new folder src/components and create our new component, src/components/HelloWorld.tsx. In this file, we'll write just 2 lines of code:

const HelloWorld = () => <h1>Hello, World!</h1>
export default HelloWorld

The first line defines our new component. It's simply a function that returns some JSX. In this case, it's our "Hello, World!" heading tag. The second line exports the component, allowing other modules to import it.

To import and use this component, load up src/App.tsx and do the following:

  • Include import HelloWorld from './components/HelloWorld' at the very top of the file to import the component
  • Use the component by having the App component return some JSX which includes <HelloWorld />.

After some refactoring, your App.tsx might look something like this:

import './App.css';
import HelloWorld from './components/HelloWorld'

const App = () => {
  return (
    <div className="App">
      <HelloWorld />
    </div>
  );
}

export default App;

In the above example, I've left the default styles, imported our HelloWorld component, and refactored the JSX which is returned by App so it includes <HelloWorld />.

Hello World in React

Adding a bit of TypeScript

Let's increase the safety of our code by using some TypeScript features.

This is what our src/components/HelloWorld.tsx looks like right now:

const HelloWorld = () => <h1>Hello, World!</h1>
export default HelloWorld

A huge benefit of TypeScript is that it can help us catch errors before you run your code instead of at run-time. Let's introduce a bug to our code:

Refactor our HelloWorld component so it looks like this:

const HelloWorld = () => "Hello, World!"
export default HelloWorld

This is valid JavaScript, but not valid React. A functional component must return JSX (or null). It can't just return a string. We're made aware of this error when Create-React-App tries to build our code.

JS-error

Let's refactor our code to use some TypeScript features so our editor notices the error:

import { FC } from 'react'
const HelloWorld: FC = () => "Hello, World!"
export default HelloWorld

The import statement imports the FunctionComponent type FC, and allows us to type our functional component to ensure it returns valid JSX. By typing the function, our editor can now immediately show us our error. Click here for more information on typing functions in TypeScript.

Throwing our first typescript error

TypeScript is explicitly telling us that we can't assign a return type of string to our functional component. Now that TypeScript has shown us this error, let's correct it.

import { FC } from 'react'
const HelloWorld: FC = () => <h1>Hello, World!</h1>
export default HelloWorld

By fixing our function and returning JSX, HelloWorld now passes TypeScript's type checks and no error is thrown. As a result, our project builds and works once more.

Benefits of TypeScript

As shown in the previous example, TypeScript can help us catch errors before we even leave our code editor. This results in faster development since TypeScript will be actively debugging your code as you write. Better to catch silly errors immediately than insanity-inducing errors during run-time.

Let's show another example of how enforcing types can help us catch errors early.

We'll create a new React component, List. This new component will take an array of string data as a prop and return some JSX which renders a <ul> tag and iterate over the list of data, rendering an <li> for each piece of data.

Here is what my version of this component could look like with more type safety from TypeScript included.

src/components/List.tsx

import { FC } from 'react'

const List: FC<{ data: string[] }> = ({ data }) => (
  <ul>
    {data.map(item => <li>{item}</li>)}
  </ul>
)

export default List

Just like our previous example, we import FC from react and use it to type our function. What we're also doing is defining a type for our data prop. In this example, we are saying that data is an array of strings. Notice how self-documenting that is. This is a huge benefit of TypeScript.

Now let's insert this component into src/App.tsx, pass into it an array of data, and sprinkle in a bit more TypeScript.

src/App.tsx

import './App.css';
import { FC } from 'react'
import HelloWorld from './components/HelloWorld'
import List from './components/List'

const avengers = [
  'Captain America',
  'Iron Man',
  'Black Widow',
  'Thor',
  'Hawkeye',
]

const App: FC = () => {
  return (
    <div className="App">
      <HelloWorld />
      <List data={avengers} />
    </div>
  );
}

export default App;

We created an avengers array, with 5 members of the Avengers team. We then passed in this list of data as a property to List. This example works because we defined List to accept an array of strings. Let's trigger a TypeScript error and send it a more varied array.

import './App.css';
import { FC } from 'react'
import HelloWorld from './components/HelloWorld'
import List from './components/List'

const avengers = [
  'Captain America',
  'Iron Man',
  'Black Widow',
  'Thor',
  'Hawkeye',
  {
    name: 'Vision',
  },
  {
    name: 'Hulk',
    color: 'green',
  },
]

const App: FC = () => {
  return (
    <div className="App">
      <HelloWorld />
      <List data={avengers} />
    </div>
  );
}

export default App;

We added 2 new heroes to the avengers array, Vision and the Hulk, but we added them as objects, not as strings. This causes TypeScript to throw an error.

Another TypeScript Error

TypeScript is telling us that our data doesn't fit the definition of the data prop. It even points out to us exactly where this prop is defined. Let's assume that we should be sending an array of objects instead of strings. Let's refactor our files so everything works again.

src/App.tsx

import './App.css';
import { FC } from 'react'
import HelloWorld from './components/HelloWorld'
import List from './components/List'

const avengers = [
  { name: 'Captain America' },
  { name: 'Iron Man' },
  { name: 'Black Widow' },
  { name: 'Thor' },
  { name: 'Hawkeye' },
  { name: 'Vision' },
  { name: 'Hulk' },
]

const App: FC = () => {
  return (
    <div className="App">
      <HelloWorld />
      <List data={avengers} />
    </div>
  );
}

export default App;

src/components/List.tsx

import { FC } from 'react'

const List: FC<{ data: { name: string }[] }> = ({ data }) => (
  <ul>
    {data.map(({ name }) => <li>{name}</li>)}
  </ul>
)

export default List

We changed our List component definition and explicitly typed the data prop to be an array of objects containing a string type name property. By explicitly typing our data prop, we know its exact shape and can catch if we send this component any prop that is shaped differently than this.

Avengers UI

Conclusion

By adding a type layer over JavaScript, TypeScript successfully saves developers time and frustration, even giving us a hand in self-documenting our code. While there surely is much more left to learn in typing your React applications, this should be a good platform to begin wading into the deep end of the pool, so to speak.

Happy coding!