Posts > Todo List Typescript
May 30, 2023
How to do simple CRUD using Typescript, React, and Vite.
Here's how our App looks like initially.
function App(){
return(
<div>
<InputForm />
<ItemList />
</div>
}
Let's add items first into our array.
import { useState } from 'react';
import InputForm from './components/InputForm';
import ItemList from './components/ItemList';
function App(){
// 1. Lets create a variable where we can store our todo items
const [todoItems, setTodoItems] = useState<string[]>([]);
// 2. We need to handle changes in our input field later as well
const [inputValue, setInputValue = useState<string>('');
// 2.a creating a handleInputChange will make sure we can update our input field
// in our form later.
const handleInputChange(e: ChangeEvent<HTMLInputElement>){
setInputValue(e.target.value);
}
//3. addItemTodo function will make sure we add our new items into the storage
const addItemTodo = () => {
const newItem = inputValue;
//3.a to overwrite and make sure we still have our old todo items when creating a new one
setTodoItems([...todoItems, newItem];
//3.b set input field back to empty state.
setInputValue('');
}
return(
<div>
<InputForm
// let's pass our functions and state into the InputForm to use.
inputValue={inputValue}
handleChange={handleInputChange}
addTodo={addItemTodo}
/>
<ItemList />
</div>
}
Let's create a form in our InputForm component that includes a text input field for writing and a button to update.
import { ChangeEvent, FormEvent } from 'react';
type Props = {
handleChange: (e: ChangeEvent<HTMLInputElement>) => void;
inputValue: string;
addTodo: () => void;
};
const InputForm = ({ handleChange, inputValue, addTodoItem }: Props) => {
//lets handle the submit of the form and trigger our addTodo function
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
addTodo();
};
return (
<form onSubmit={handleSubmit}>
<input
type='text'
name='todo'
placeholder='Enter stuff here'
value={inputValue}
onChange={handleChange}
/>
<button type='submit'>Add +</button>
</form>
);
};
export default InputForm;
Let's show the todo list in our App using the ItemList
component
...
// we just need to pass our storage state into the component
<ItemList todoItems={todoItems} />
...
}
In our ItemList
component, let's add a ul
and display our items in li
type Props = {
todoItems: string[];
};
const ItemList = ({ todoItems }: Props) => {
return (
<ul>
{// everytime we update our todoItems we render our component}
{todoItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
};
export default ItemList;
I plan to update my todo list by creating a component that triggers an input field whenever I click the edit button. This enables me to edit my to-do items easily.
// onUpdateTodo will take a todo item index and new text
// then it'll update that item based on the index
const onUpdateTodo = (index: number, newItem: string) => {
const updatedItem = [...todoItems];
updatedItem[index] = newItem;
setTodoItems(updatedItem);
};
//...
// just need to pass our onUpdateTodo to the component
<List
todoItems={todoItems}
deleteTodoItem={deleteTodoItem}
onUpdateTodo={onUpdateTodo}
/>
//...
const List = ({ todoItems, deleteTodoItem, onUpdateTodo }: Props) => {
//1. lets create state that will hold our todo item index number and text
const [editIndex, setEditIndex] = useState<number | null>(null);
const [editText, setEditText] = useState('');
//2. handleEdit will populate our state when we clicked on Edit button for a
// todo item and will make our input field visible
const handleEdit = (index: number, currentText: string) => {
setEditIndex(index);
setEditText(currentText);
};
//3. handle save will call the onUpdateTodo function to change
// the text of our item we selected
const handleSave = (index: number) => {
onUpdateTodo(index, editText);
setEditIndex(null);
setEditText('');
};
//4. handleCancle just cancels the edit and make our state as initial
const handleCancel = () => {
setEditIndex(null);
setEditText('');
};
return (
<ul>
{todoItems.map((item, index) => (
<li key={index}>
{editIndex === index ? (
<>
<input
type='text'
value={editText}
onChange={(e) => setEditText(e.target.value)}
/>
<button onClick={() => handleSave(index)}>Save</button>
<button onClick={handleCancel}>Cancel</button>
</>
) : (
<>
{item}
<button onClick={() => handleEdit(index, item)}>Edit</button>
<button onClick={() => deleteTodoItem(index)}>Delete</button>
</>
)}
</li>
))}
</ul>
);
};
We add a new function to delete our item on the list.
const deleteTodoItem = (index: number) => {
//1. create a temporary variables for our list item
const updatedItems = [...todoItems];
//2. remove 1 item based on index using splice
updatedItems.splice(index, 1);
//3. Update our state with new / updated variable
setTodoItems(updatedItems);
};
//...
<ItemList todoItems={todoItems} deleteTodoItem={deleteTodoItem} />
//...
In our ItemList component, we can pass the item's index number into the deleteTodoItem function.
type Props = {
todoItems: string[];
deleteTodoItem: (index: number) => void;
};
//...
<ul>
{todoItems.map((item, index) => (
<li key={index}>
{item} <button onClick={() => deleteTodoItem(index)}>x</button>
</li>
))}
</ul>