DEV Community

Husnain Mustafa
Husnain Mustafa

Posted on

Let's make your first React Typescript Todo web app

When I started React with typescript, I found it a bit difficult to code as I had little to no experience in typescript. So I had to go through the web and understand the basics.

Typescript is not that hard as it looks like, it's just a superset of Javascript. So if you know Javascript, it would be really easy to understand.

So let's get started.

Creating React App through Vite

At first, most react developers, including myself, used to create the project through Create-React-App. But this package got deprecated and it is no further updated.

So I shifted to Vite, which is pretty good and much faster.

Before creating React App, you must have Vite installed. You can install Vite by the following command in the terminal:
npm i -g vite

To create a React App:

  1. Open Terminal
  2. Type: npm create vite@latest app-name, app-name is the name of the app, in our case, it is 'todo'. So command becomes npm create vite@latest todo
  3. Now it will ask which template we want to use, select 'React' with arrow keys and press enter
  4. Now it will ask for the language, select 'Typescript'
  5. It will create a folder named 'todo'.
  6. Move to folder by cd todo
  7. Install the required packages by npm install
  8. You can run the basic Vite project by npm run dev and open the browser and type in search bar: localhost:5173

Installing Frontend Libraries

We will be using MUI for this project as it provides variety of components and we do not need to make components from the scratch. So type the following in the terminal:
npm install @mui/material @emotion/react @emotion/styled

Structuring the directory

As our setup is done, now is the time for developing the actual project.
For that, I usually start with making some directory structure.
Below is the directory structure I made for this project:

Image description

It is not necessary to make the directory structure at the start of the project. Directory structure is always being updated as the project grows.

Adding Dummy Data

As we are only on the front-end, i.e without any back-end, we would need to create the dummy data.

But before that, we need to make an interface which corresponds to dummy data. An interface defines the syntax that any entity must adhere to. (Definition from tutorialspoint)

So make a file in '/commonInterfaces' by name index.ts and copy paste the interface below:

export interface TodoListProps {
    id: number,
    checked: boolean,
    title: string,
    description: string
}
Enter fullscreen mode Exit fullscreen mode

As interfaces provides a syntax, so it should be having the attributes which our object have. In this case, we know that todo would have title as string, description as string, checked as boolean and finally the id as number

Moving on to adding Dummy Data
So in '/dummydata' directory, make a file named index.ts and paste the dummy data provided below:

import { TodoListProps } from "../commonInterfaces";

export const DummyTodoList: TodoListProps[] = [
    {
        id: 1,
        checked: false,
        title: 'Todo 1',
        description: 'This is Todo 1'
    },
    {
        id: 2,
        checked: false,
        title: 'Todo 2',
        description: 'This is Todo 2'
    },
    {
        id: 3,
        checked: false,
        title: 'Todo 3',
        description: 'This is Todo 3'
    },
    {
        id: 4,
        checked: true,
        title: 'Todo 4',
        description: 'This is Todo 4'
    },
    {
        id: 5,
        checked: true,
        title: 'Todo 5',
        description: 'This is Todo 5'
    },
]

Enter fullscreen mode Exit fullscreen mode

Making Todo Page

Now, it's time to make a todo page which will be a wrapper for all todo related components

So make a directory in '/pages' by name 'todo' and make a file in '/pages/todo/' by name 'index.tsx'.

Styled Components

First, let's make the styled component as wrapper:

import { styled } from "@mui/material";
const FlexCenter = styled('div')(() => ({
    display: 'flex',
    justifyContent: 'center',
    width: '100%'
}))

Enter fullscreen mode Exit fullscreen mode

Now, let's make the styled component of the title:

const Title = styled('h1')( ({theme}) => ({
    color: theme.palette.primary.main 
}))

Enter fullscreen mode Exit fullscreen mode

If you do not know what are styled components and how to make them, you can read it on MUI documentation

Styled component is just another way of writing css for the component.

Making Todo Page Component

export const TodoPage = () => {

    return(
        <Grid container sx = {{px: 1, justifyContent: 'space-around'}}>
            <Grid item xs = {5}>
                <FlexCenter>
                    <div style = {{width: '100%'}}>
                        <Title>
                            Todo
                        </Title>
                        {todoList.filter(el => el.checked == false).map((el)=>{
                            return(
                                <TodoCardComponent key = {el.id} {...el} toggleCheck={toggleCheck} deleteTodo = {deleteTodo} isEditing = {el.id == editIndex} setEditIndex={setEditIndex} saveEdit = {saveEdit}/>
                            )
                        })}
                    </div>
                </FlexCenter>
            </Grid>
            <Grid item xs = {5}>
                <FlexCenter>
                    <div style = {{width: '100%'}}>
                        <Title>
                            Done
                        </Title>
                        {todoList.filter(el => el.checked == true).map((el)=>{
                            return(
                                <TodoCardComponent key = {el.id} {...el} toggleCheck={toggleCheck} deleteTodo = {deleteTodo} isEditing = {el.id == editIndex} setEditIndex={setEditIndex} saveEdit = {saveEdit}/>
                            )
                        })}
                    </div>
                </FlexCenter>
            </Grid>
        </Grid>
    )
} 
Enter fullscreen mode Exit fullscreen mode

Now we made the component but many things are missing from it. You can see that we are using the following:

  1. todoList/setTodoList, a state responsible for storing the current todo list
  2. editIndex/setEditIndex, a state responsible for storing the index of the todo item which is currently being in edit mode, if none is in edit mode, it would be -1
  3. toggleCheck, a funciton responsible to update the checked/unchecked status of a single todo
  4. deleteTodo, a fucntion responsible for deleting a single todo
  5. saveEdit, a funciton responsible for saving the edited todo

These were never defined but us, but used in the above code. Through these, we will achieve the functionality of our Todo App so let's define these. Below is the whole file with all these missing items. So you can copy paste the below code into /pages/todo/index.tsx

import { Grid, styled } from "@mui/material";
import { useCallback, useState } from "react";
import { DummyTodoList } from "../../dummydata";
import { TodoCardComponent } from "../../components/todoCard";


const FlexCenter = styled('div')(() => ({
    display: 'flex',
    justifyContent: 'center',
    width: '100%'
}))

const Title = styled('h1')( ({theme}) => ({
    color: theme.palette.primary.main 
}))



export const TodoPage = () => {

    const [todoList, setTodoList] = useState(DummyTodoList)
    const [editIndex, setEditIndex] = useState(-1)

    const toggleCheck = useCallback((id: number) => {
        const tempTodoList = todoList
        const index = tempTodoList.findIndex(e => e.id == id)
        tempTodoList[index].checked = !tempTodoList[index].checked
        setTodoList([...tempTodoList])
    }, [todoList])

    const deleteTodo = useCallback((id: number) => {
        let tempTodoList = todoList
        tempTodoList = tempTodoList.filter(el => el.id != id)
        setTodoList([...tempTodoList])
    }, [todoList])

    const saveEdit = useCallback((id: number, title: string, description: string) => {
        const tempTodoList = todoList
        const index = tempTodoList.findIndex(e => e.id == id)
        tempTodoList[index].title = title
        tempTodoList[index].description = description
        setTodoList([...tempTodoList])
        setEditIndex(-1)
    }, [todoList])

    return(
        <Grid container sx = {{px: 1, justifyContent: 'space-around'}}>
            <Grid item xs = {5}>
                <FlexCenter>
                    <div style = {{width: '100%'}}>
                        <Title>
                            Todo
                        </Title>
                        {todoList.filter(el => el.checked == false).map((el)=>{
                            return(
                                <TodoCardComponent key = {el.id} {...el} toggleCheck={toggleCheck} deleteTodo = {deleteTodo} isEditing = {el.id == editIndex} setEditIndex={setEditIndex} saveEdit = {saveEdit}/>
                            )
                        })}
                    </div>
                </FlexCenter>
            </Grid>
            <Grid item xs = {5}>
                <FlexCenter>
                    <div style = {{width: '100%'}}>
                        <Title>
                            Done
                        </Title>
                        {todoList.filter(el => el.checked == true).map((el)=>{
                            return(
                                <TodoCardComponent key = {el.id} {...el} toggleCheck={toggleCheck} deleteTodo = {deleteTodo} isEditing = {el.id == editIndex} setEditIndex={setEditIndex} saveEdit = {saveEdit}/>
                            )
                        })}
                    </div>
                </FlexCenter>
            </Grid>
        </Grid>
    )
} 
Enter fullscreen mode Exit fullscreen mode

We are using TodoCardComponent in the above component, but we never made it. So let's make it.

Making of Todo Card Component

We need to make a component which will be responsible to render the single todo. By doing this, we will be able reuse this component for all the list of todo.

So make a folder in '/components' named 'todoCard', i.e it would become: '/components/todoCard/'. Here make a new file named 'index.jsx' with the following code:

import { Button, Card, Checkbox, Grid, TextField, Typography, styled } from "@mui/material";
import { useRef } from "react";
import { TodoListProps } from "../../commonInterfaces";


const CustomCard = styled(Card)(({ theme }) => ({
    padding: theme.spacing(1),
    borderRadius: theme.shape.borderRadius,
    width: '100%',
    marginBottom: theme.spacing(1),
    marginTop: theme.spacing(1)
}));

interface TodoCardProps extends TodoListProps {
    toggleCheck(id: number): void,
    deleteTodo(id: number): void,
    setEditIndex(id: number): void,
    saveEdit(id: number, title: string, description: string): void,
    isEditing: boolean
}



export const TodoCardComponent = ({id, checked, title, description, toggleCheck, deleteTodo, isEditing, setEditIndex, saveEdit}: TodoCardProps) => {

    const titleRef = useRef<HTMLInputElement>(null)
    const descriptionRef = useRef<HTMLInputElement>(null)

    return(
        <CustomCard elevation={3}>
            <Grid container>
                <Grid item>
                    <Checkbox onChange={()=>{toggleCheck(id)}} checked = {checked}></Checkbox>
                </Grid>
                <Grid item xs = {8}>
                    {!isEditing ?
                        <>
                            <Typography variant = 'h6' style = {{textDecoration: checked ? 'line-through' : 'none', color: checked ? 'lightgray' : 'black'}}>
                                {title}
                            </Typography>
                            <Typography variant = 'body1' style = {{textDecoration: checked ? 'line-through' : 'none', color: checked ? 'lightgray' : 'black'}}>
                                {description}
                            </Typography>
                            <Button onClick = {()=>{setEditIndex(id)}} variant = 'contained' color = 'info' sx = {{marginBottom: 1}}>
                                Edit
                            </Button>
                        </>
                        :
                        <>
                            <TextField inputRef = {titleRef} defaultValue={title} sx = {{marginBottom: 1}}/>
                            <TextField inputRef = {descriptionRef} defaultValue={description} sx = {{marginBottom: 1}}/>
                            <br/>
                            <Button onClick={()=>{if(titleRef.current && descriptionRef.current)saveEdit(id, titleRef.current.value, descriptionRef.current.value)}} variant = 'contained' color = 'success' sx = {{marginBottom: 1}}>
                                Save
                            </Button>
                            <br/>
                            <Button onClick={()=>{setEditIndex(-1)}} variant = 'contained' color = 'warning'>
                                Discard
                            </Button>
                        </>
                    }
                </Grid>
                {!isEditing &&
                    <Grid item xs = {2}>
                        <Button variant="contained" onClick={()=>{deleteTodo(id)}} color="error">
                            Delete
                        </Button>
                    </Grid>
                }
            </Grid>
        </CustomCard>
    )
}
Enter fullscreen mode Exit fullscreen mode

Rendering the Todo Page in App.tsx

Finally we need to render our page in so that it can be shown to us in '/' path.
Open 'App.tsx' and paste the code below:

import { TodoPage } from "./pages/todo"
import './App.css'

function App() {

  return (
    <TodoPage/>
  )
}

export default App

Enter fullscreen mode Exit fullscreen mode

That's it. Now our Todo Page component will get rendered at '/' path.
Now run the dev server by npm run dev if it is not already running and open the browser and type: localhost:5173

This is what it should look like:
Image description

Assignment

Notice that I have not added the 'Create New todo' feature in this app. Play around the code and try to make it yourself.

Repository Link

Here is the repo from which you can clone the project and play around.


Hope it helped you understanding the basics. Keep learning. ❤️

Top comments (0)