Creating Your First React Typescript Project from Scratch
Tags: react, typescript, webdev, javascript
Published: 2020-06-06 22:28:12

---
title: Creating Your First React Typescript Project from Scratch
published: true
description: Today we’re going to learn how to write a React Typescript application. As is tradition with any new front-end language or framework, we’re going to learn how to write a to-do list app!
tags: react, typescript, webdev, javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/m3cuh3q0lajrt4pjxgeh.png
canonical_url: https://typeofnan.dev/your-first-react-typescript-project-todo-app
---

Today we're going to learn how to write a React Typescript application. As is tradition with any new front-end language or framework, we're going to learn how to write a to-do list app! Despite to-do list app tutorials being overdone, I like using it since you can make an apples-to-apples comparison with other frameworks in which you've also written the same app.
<hr />

## If you enjoy this tutorial, please give it a 💓, 🦄, or 🔖 and consider:

- signing up for [my free weekly dev newsletter](https://buttondown.email/devtuts)
- subscribing to [my free YouTube dev channel](https://www.youtube.com/c/devtutsco?sub_confirmation=1)

<hr />
# Other Resources to Accompany this Tutorial

This tutorial has an accompanying github repository! Also, I recorded a three-part YouTube tutorial series if that's your preferred tutorial consumption method. Both can be found below:

- [Github repo](https://github.com/nas5w/react-typescript-todo-app)
- [YouTube tutorial series](https://www.youtube.com/watch?v=ODvirqIC09A)

# Getting Your Environment Setup

To get started, there are a couple prerequisites. First, you'll need to install Node if you don't have it yet.

- [Install nodejs](https://nodejs.org/en/download/)

Make sure you have node installed by typing `node -v` into your command line. You should see a version display. My version is currently 10.15.2, but yours might be different.

```bash
node -v
```

We _could_ use npm to manage our node packages, but I prefer yarn. Therefore, I'm going to use npm to install yarn globally: `npm i -g yarn`

```bash
npm i -g yarn
```

If this works, you should be able to see your yarn version by typing `yarn -v`. Again, your version may be different than mine:

```bash
yarn -v
```

Now we're ready to get going!

# Bootstrapping with create-react-app

To save us the setup headache and get us moving faster, we can bootstrap our app with `create-react-app`! I've used React in production a lot, and I _still_ generally start out with `create-react-app` as a template to get going.

Let's use yarn to create a react. We'll need to make sure to specify that we want to use Typescript and that we want to name our app `todo-list`:

```bash
yarn create react-app todo-list --template typescript
```

You should see a bunch of downloading happening and finally instructions to `cd` into your new directory and start coding. Let's go!

# Exploring the Bootstrapped App

Make sure you're in the new `todo-list` directory. Your should see the following folders and files. While we'll be doing most of our work in the `src` folder, it's important to understand what everything else does. Here's a quick rundown:

- **node_modules** - contains code for the third-party libraries used by your app.
- **public** - contains the assets that'll help build your final app, including things like `index.html` and your app's favicon.
- **src** - contains the source code for the app you'll be working with most.
- **.gitignore** - specifies which files to ignore for source control.
- **package.json** - contains your app's configuration, including things like dependencies and scripts.
- **README.md** - starts with information about create-react-app, but in a real application you should describe the app itself.
- **tsconfig.json** - contains configuration for the typescript compiler.
- **yarn.lock** - contains the exact version for all project dependencies. Should be checked into version control.

## Start the app

Great, that's enough looking around. Let's start the app by running `yarn start` in the command prompt.

Navigate to `http://localhost:3000` and you should see our app in all its starter glory:

![default create-react-app](https://dev-to-uploads.s3.amazonaws.com/i/up12pidg4w2gwfe8b0jy.png)

**Note:** As part of the deal with create-react-app, our app will hot reload whenever we make changes! That means we can generally leave `yarn start` running in the console and we won't have to restart it. Realistically, we'll find that our app occasionally does require us to restart the server when the typescript compiler barfs or we add or remove files.

# Stripping Down to "Hello World"

This is cool and all, but we want to start relatively fresh with this tutorial. Therefore, we're going to remove a bunch of files from the `src` folder and modify some files.

**Remove files**

```bash
cd src
rm App.css App.test.tsx index.css logo.svg serviceWorker.ts setupTests.ts
```

The only files left should be `App.tsx`, `index.tsx`, and `react-app-env.d.ts`.

**Edit code**

Let's first hop into `index.tsx` and remove references to `index.css` and the service worker. Your file should end up looking like this:

```tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
```

Next, open up `App.tsx` and delete references to the logo and CSS file. Also, delete everything from the `App` function and instead replace it with returning a React fragment with the text "Hello World" in it.

```tsx
import React from 'react';

function App() {
  return <>Hello World</>;
}

export default App;
```

Now check out our app!

![hello world](https://dev-to-uploads.s3.amazonaws.com/i/zqir4frd1ixkg3aph2xs.png)

# A Quick Mock of Our Todo List Application

One nice aspect of React is your component structure can often closely follow your design. In our todo list application example, we can pretent we're given the following mock:

![todo list app mock](https://dev-to-uploads.s3.amazonaws.com/i/gepesdakvuvfo5ej596m.png)

Importantly, we can see our app has a `TodoListItem`, a `TodoList`, and an `AddTodoForm`. Ultimately, our app structure will end up mirroring this.

# Creating a Todo List Item

Let's get to work! Create a new file called `TodoListItem.tsx` in your `src` folder.

Let's write a basic React component that has placeholder content inside a list item:

```tsx
import React from 'react';

export const TodoListItem = () => {
  return <li>content</li>;
};
```

Cool. Now, let's Add some props. This is where we'll start getting typescripty! Our `TodoListItem` component will at least take a `todo` item as a prop. That `todo` item will have `text` property, which will be a `string`, and a `complete` property, which will be a `boolean`.

Once we've defined our props, we can declare our `TodoListItem` as a functional component (`React.FC`) and then pass our `Props` as a generic.

```tsx
import React from 'react';

interface Todo {
  text: string;
  complete: boolean;
}

interface Props {
  todo: Todo;
}

export const TodoListItem: React.FC<Props> = props => {
  return <li>content</li>;
};
```

Next, let's actually use the props that we've described. Let's put a checkbox in each list item. The checkbox will be checked when `todo.complete` is `true`. A label will be populated with our `todo.text`.

Additionally, let's put a strike-through our todo item if it's complete. We can do this with the `style` attribute.

```tsx
import React from 'react';

interface Todo {
  text: string;
  complete: boolean;
}

interface Props {
  todo: Todo;
}

export const TodoListItem: React.FC<Props> = ({ todo }) => {
  return (
    <li>
      <label
        style={{ textDecoration: todo.complete ? 'line-through' : undefined }}
      >
        <input type="checkbox" checked={todo.complete} /> {todo.text}
      </label>
    </li>
  );
};
```

## Creating a Type Declaration File

While we could keep `Todo` declared in this file, it's going to be used throughout the app. We can either export it here and import it everywhere else we need it throughout the app, _or_ we can create a type declaration file. Let's call this `types.d.ts` and put it in our `src` folder. What's nice about a `*.d.ts` file is our compiler will recognize the types in there as global to the project and we won't need to explicitly import or export them.

**types.d.ts**

```typescript
interface Todo {
  text: string;
  complete: boolean;
}
```

Now we can remove the `Todo` interface declared in `TodoListItem.tsx` and everything should still work just fine.

## Including The TodoListItem in our App

Of course we've only written a component so far; we still need to include it in our App. let's do that now. Go to `App.tsx` and import the component.

```tsx
import React from 'react';
import { TodoListItem } from './TodoListItem';

function App() {
  return (
    <>
      <TodoListItem />
    </>
  );
}

export default App;
```

You may notice that, if we try to run our app right now, it'll fail to compile&ndash;we defined our `TodoListItem` as taking a `todo` prop and we're not providing it! Let's change that: we'll make a `Todos` array.

We'll create two items and put them within an unordered list:

```jsx
import React from 'react';
import { TodoListItem } from './TodoListItem';

const todos: Todo[] = [
  {
    text: 'Walk the dog',
    complete: false,
  },
  {
    text: 'Write app',
    complete: true,
  },
];

function App() {
  return (
    <ul>
      <TodoListItem todo={todos[0]} />
      <TodoListItem todo={todos[1]} />
    </ul>
  );
}

export default App;
```

Now let's check out our app in the browser:

![beginning of the todo app](https://dev-to-uploads.s3.amazonaws.com/i/vtkcihntnjqaj9d2jaw6.png)

# Toggling Todos

The next thing we want to do is actually be able to toggle the todo items. We can no longer rely on our `todos` array, but we'll rather need some state to manage things. For this, we'll use the `useState` React hook in our `App.tsx` file. We can rename our `todos` array to `initialTodos` since it really just represents the initial state.

```tsx
import React, { useState } from 'react';
import { TodoListItem, Todo } from './TodoListItem';

const initialTodos: Todo[] = [
  {
    text: 'Walk the dog',
    complete: false,
  },
  {
    text: 'Write app',
    complete: true,
  },
];

function App() {
  const [todos, setTodos] = useState(initialTodos);
  return (
    <ul>
      <TodoListItem todo={todos[0]} />
      <TodoListItem todo={todos[1]} />
    </ul>
  );
}

export default App;
```

We'll want to be able to toggle todos. We can do this by creating a `toggleTodo` function in our `App.tsx` file. The `toggleTodo` function will take a selected todo and toggle the `complete` prop for that todo.

Then, we can pass `toggleTodo` to each `TodoListItem`.

```tsx
import React, { useState } from 'react';
import { TodoListItem } from './TodoListItem';

const initialTodos: Todo[] = [
  {
    text: 'Walk the dog',
    complete: false,
  },
  {
    text: 'Write app',
    complete: true,
  },
];

function App() {
  const [todos, setTodos] = useState(initialTodos);

  const toggleTodo = (selectedTodo: Todo) => {
    const newTodos = todos.map(todo => {
      if (todo === selectedTodo) {
        return {
          ...todo,
          complete: !todo.complete,
        };
      }
      return todo;
    });
    setTodos(newTodos);
  };

  return (
    <ul>
      <TodoListItem todo={todos[0]} toggleTodo={toggleTodo} />
      <TodoListItem todo={todos[1]} toggleTodo={toggleTodo} />
    </ul>
  );
}

export default App;
```

Our linter is now mad. That's because `toggleTodo` is not an expected prop for our `TodoListItem`. Let's add it as an expected prop. While we're at it, let's declare a `ToggleTodo` type in our `types.d.ts` file:

**types.d.ts**

```typescript
interface Todo {
  text: string;
  complete: boolean;
}

type ToggleTodo = (selectedTodo: Todo) => void;
```

Now when we add `toggleTodo` as a prop for the `TodoListItem`, let's execute it in the `onClick` handler for our `input` element.

**TodoListItem.tsx**

```tsx
import React from 'react';

interface Props {
  todo: Todo;
  toggleTodo: ToggleTodo;
}

export const TodoListItem: React.FC<Props> = ({ todo, toggleTodo }) => {
  return (
    <li>
      <label
        style={{ textDecoration: todo.complete ? 'line-through' : undefined }}
      >
        <input
          type="checkbox"
          checked={todo.complete}
          onClick={() => {
            toggleTodo(todo);
          }}
        />{' '}
        {todo.text}
      </label>
    </li>
  );
};
```

Let's open our app and start toggling our todo items. It works!

![toggling todos](https://dev-to-uploads.s3.amazonaws.com/i/xwgk3a805rlm8ir2bsco.gif)

# Creating a TodoList Component

If you recall, our app mock include a `TodoList` component to contain all our todos.

![todo list app mock](https://dev-to-uploads.s3.amazonaws.com/i/gepesdakvuvfo5ej596m.png)

Let's create that component. It's going to have to take the following props:

- List of `todos` to map over
- The `toggleTodo` function to pass to each todo item.

Importent to note in this component is that we're mapping over our `todos` rather than listing them individually. This is obviously a good idea because we could theoretically have any number of `todos`. Note that, when we iterate over the `todos`, we pass each `TodoListItem` a `key` prop. This is needed by React's diffing algorithm to reconcile arrays of elements.

**TodoList.tsx**

```tsx
import React from 'react';
import { TodoListItem } from './TodoListItem';

interface Props {
  todos: Todo[];
  toggleTodo: ToggleTodo;
}

export const TodoList: React.FC<Props> = ({ todos, toggleTodo }) => {
  return (
    <ul>
      {todos.map(todo => (
        <TodoListItem key={todo.text} todo={todo} toggleTodo={toggleTodo} />
      ))}
    </ul>
  );
};
```

Now, we can replace much of the code we had in our `App.tsx` file with our `TodoList`. We have to remember to pass the right props to it&ndash;although if we forget, the typescript compiler will yell at us, which is great!

**App.tsx**

```tsx
import React, { useState } from 'react';
import { TodoList } from './TodoList';

const initialTodos: Todo[] = [
  {
    text: 'Walk the dog',
    complete: false,
  },
  {
    text: 'Write app',
    complete: true,
  },
];

function App() {
  const [todos, setTodos] = useState(initialTodos);

  const toggleTodo = (selectedTodo: Todo) => {
    const newTodos = todos.map(todo => {
      if (todo === selectedTodo) {
        return {
          ...todo,
          complete: !todo.complete,
        };
      }
      return todo;
    });
    setTodos(newTodos);
  };

  return <TodoList todos={todos} toggleTodo={toggleTodo} />;
}

export default App;
```

If we open up our app in the browser, we should be able to confirm that everything's working.

# Adding Todo Items

Let's make a new component called `AddTodoForm` so we can add todo items. For now, we'll just make a form that doesn't do anything and add it to our `App.tsx` file.

**AddTodoForm.tsx**

```tsx
import React from 'react';

export const AddTodoForm: React.FC = () => {
  return (
    <form>
      <input type="text" />
      <button type="submit">Add Todo</button>
    </form>
  );
};
```

**App.tsx**

```tsx
import React, { useState } from 'react';
import { TodoList } from './TodoList';
import { AddTodoForm } from './AddTodoForm';

const initialTodos: Todo[] = [
  {
    text: 'Walk the dog',
    complete: false,
  },
  {
    text: 'Write app',
    complete: true,
  },
];

function App() {
  const [todos, setTodos] = useState(initialTodos);

  const toggleTodo = (selectedTodo: Todo) => {
    const newTodos = todos.map(todo => {
      if (todo === selectedTodo) {
        return {
          ...todo,
          complete: !todo.complete,
        };
      }
      return todo;
    });
    setTodos(newTodos);
  };

  return (
    <>
      <TodoList todos={todos} toggleTodo={toggleTodo} />
      <AddTodoForm />
    </>
  );
}

export default App;
```

Now we can see in our browser the form appears. When we try to add a todo and click submit, nothing really happens except the page reloads.

![with form](https://dev-to-uploads.s3.amazonaws.com/i/5075hbtngxnikqyuztrc.png)

Now, let's make our form add stuff. First, we can create an `addTodo` function in our `App.tsx` file that will eventually be passed down to our form. We can declare the type `AddTodo` in our `types.d.ts` file.

Since each new `todo` will start out incomplete, we really just need to `text` prop to create one.

**types.d.ts**

```tsx
interface Todo {
  text: string;
  complete: boolean;
}

type ToggleTodo = (selectedTodo: Todo) => void;

type AddTodo = (text: string) => void;
```

**App.tsx**

```tsx
import React, { useState } from 'react';
import { TodoList } from './TodoList';
import { AddTodoForm } from './AddTodoForm';

const initialTodos: Todo[] = [
  {
    text: 'Walk the dog',
    complete: false,
  },
  {
    text: 'Write app',
    complete: true,
  },
];

function App() {
  const [todos, setTodos] = useState(initialTodos);

  const toggleTodo: ToggleTodo = (selectedTodo: Todo) => {
    const newTodos = todos.map(todo => {
      if (todo === selectedTodo) {
        return {
          ...todo,
          complete: !todo.complete,
        };
      }
      return todo;
    });
    setTodos(newTodos);
  };

  const addTodo: AddTodo = (text: string) => {
    const newTodo = { text, complete: false };
    setTodos([...todos, newTodo]);
  };

  return (
    <>
      <TodoList todos={todos} toggleTodo={toggleTodo} />
      <AddTodoForm addTodo={addTodo} />
    </>
  );
}

export default App;
```

Again, we'll hit a familiar compilation error at this point: `AddTodoFrom` doesn't expect an `addTodo` prop, so the compiler barfs. Good! Let's fix that up by adding the prop to our `AddTodoForm`.

```tsx
import React from 'react';

interface Props {
  addTodo: AddTodo;
}

export const AddTodoForm: React.FC<Props> = ({ addTodo }) => {
  return (
    <form>
      <input type="text" />
      <button type="submit">Add Todo</button>
    </form>
  );
};
```

Now our compiler error is gone, but our form still doesn't do anything. To get it to work, we'll do a number of things:

1. Maintain internal `text` state using `useState`. This will allow us to maintain the state of the new todo item's text.
2. Bind `text` to the `input` value.
3. Set text using `setText` in the input's `onChange` handler. `e.target.value` contains the current value.
4. Add an `onClick` handler to the submit button to submit the typed text.
5. Make sure to cancel the default event of actually submitting the form.
6. Add the todo using `addTodo` and passing it `text`.
7. Clearing our form by setting `text` to an empty string.

```tsx
import React, { useState } from 'react';

interface Props {
  addTodo: AddTodo;
}

export const AddTodoForm: React.FC<Props> = ({ addTodo }) => {
  const [text, setText] = useState('');

  return (
    <form>
      <input
        type="text"
        value={text}
        onChange={e => {
          setText(e.target.value);
        }}
      />
      <button
        type="submit"
        onClick={e => {
          e.preventDefault();
          addTodo(text);
          setText('');
        }}
      >
        Add Todo
      </button>
    </form>
  );
};
```

And there you have it! If you go back to the application, you should now be able to add new todo items and interact with them.

![finished app](https://dev-to-uploads.s3.amazonaws.com/i/yt8hlwtv3xo73eziyli1.gif)

# Conclusion

Thanks for following along! This should hopefully give you a nice head start on your journey to using React with Typescript to craft incredible user interfaces.