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:
- Open Terminal
- Type:
npm create vite@latest app-name
, app-name is the name of the app, in our case, it is 'todo'. So command becomesnpm create vite@latest todo
- Now it will ask which template we want to use, select 'React' with arrow keys and press enter
- Now it will ask for the language, select 'Typescript'
- It will create a folder named 'todo'.
- Move to folder by
cd todo
- Install the required packages by
npm install
- 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:
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
}
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'
},
]
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%'
}))
Now, let's make the styled component of the title:
const Title = styled('h1')( ({theme}) => ({
color: theme.palette.primary.main
}))
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>
)
}
Now we made the component but many things are missing from it. You can see that we are using the following:
- todoList/setTodoList, a state responsible for storing the current todo list
- 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
- toggleCheck, a funciton responsible to update the checked/unchecked status of a single todo
- deleteTodo, a fucntion responsible for deleting a single todo
- 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>
)
}
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>
)
}
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
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:
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)