Posts > Todo List Typescript

Todo List Typescript

How to do simple CRUD using Typescript, React, and Vite.

Simple Layout

Here's how our App looks like initially.

function App(){

  return(
      <div>
          <InputForm />
          <ItemList />
      </div>
}

Create Item

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;

Read ( Displaying the Todo Items )

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;

Updating an item in our list

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>
  );
};

Deleting an item in our list

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>

Tags 🏷