Skip to content

CRUD Operations

Adding new tasks

Now that we can see the list of tasks, it's time to add a few more.

Add the highlighted newTaskTitle state and addTask function the App Component

ts
// src/App.tsx

export default function App() {
  const [tasks, setTasks] = useState<Task[]>([])
  const [newTaskTitle, setNewTaskTitle] = useState("")

  const addTask = async (e: FormEvent) => {
    e.preventDefault()
    try {
      const newTask = await taskRepo.insert({ title: newTaskTitle })
      setTasks([...tasks, newTask])
      setNewTaskTitle("")
    } catch (error: unknown) {
      alert((error as { message: string }).message)
    }
  }
  //...
  • the call to taskRepo.insert will make a post request to the server, insert the new task to the db, and return the new Task object with all it's info (including the id generated by the database)

Import FormEvent

This code requires adding an import of FormEvent from react.

Next let's adjust the tsx to display a form to add new tasks

tsx
// src/App.tsx

return (
  <div>
    <h1>Todos</h1>
    <main>
      <form onSubmit={addTask}>
        <input
          value={newTaskTitle}
          placeholder="What needs to be done?"
          onChange={e => setNewTaskTitle(e.target.value)}
        />
        <button>Add</button>
      </form>
      {tasks.map(task => {
        return (
          <div key={task.id}>
            <input type="checkbox" checked={task.completed} />
            {task.title}
          </div>
        )
      })}
    </main>
  </div>
)

Try adding a few tasks to see how it works

Mark Tasks as completed

Modify the contents of the tasks.map iteration within the App component to include the following setCompleted function and call it in the input's onChange event.

tsx
// src/App.tsx

{
  tasks.map(task => {
    const setTask = (value: Task) =>
      setTasks(tasks => tasks.map(t => (t === task ? value : t)))

    const setCompleted = async (completed: boolean) =>
      setTask(await taskRepo.save({ ...task, completed }))

    return (
      <div key={task.id}>
        <input
          type="checkbox"
          checked={task.completed}
          onChange={e => setCompleted(e.target.checked)}
        />
        {task.title}
      </div>
    )
  })
}
  • The setTask function is used to replace the state of the changed task in the tasks array
  • The taskRepo.save method update the task to the server and returns the updated value

Rename Tasks and Save them

To make the tasks in the list updatable, we'll bind the tasks React state to input elements and add a Save button to save the changes to the backend database.

Modify the contents of the tasks.map iteration within the App component to include the following setTitle and saveTask functions and add an input and a save button.

tsx
// src/App.tsx

{
  tasks.map(task => {
    const setTask = (value: Task) =>
      setTasks(tasks => tasks.map(t => (t === task ? value : t)))

    const setCompleted = async (completed: boolean) =>
      setTask(await taskRepo.save({ ...task, completed }))

    const setTitle = (title: string) => setTask({ ...task, title })

    const saveTask = async () => {
      try {
        setTask(await taskRepo.save(task))
      } catch (error: unknown) {
        alert((error as { message: string }).message)
      }
    }

    return (
      <div key={task.id}>
        <input
          type="checkbox"
          checked={task.completed}
          onChange={e => setCompleted(e.target.checked)}
        />
        <input value={task.title} onChange={e => setTitle(e.target.value)} />
        <button onClick={saveTask}>Save</button>
      </div>
    )
  })
}
  • The setTitle function, called from the input's onChange event, saves the value from the input to the tasks state.
  • The saveTask function, called from the button's' onClickevent, saves the task object to the backend.

Make some changes and refresh the browser to verify the backend database is updated.

Browser's Network tab

As you play with these CRUD capabilities, monitor the network tab and see that they are all translated to rest api calls.

Delete Tasks

Let's add a Delete button next to the Save button of each task in the list.

Add the highlighted deleteTask function and Delete button Within the tasks.map iteration in the return section of the App component.

tsx
// src/App.tsx

{
  tasks.map(task => {
    const setTask = (value: Task) =>
      setTasks(tasks => tasks.map(t => (t === task ? value : t)))

    const setCompleted = async (completed: boolean) =>
      setTask(await taskRepo.save({ ...task, completed }))

    const setTitle = (title: string) => setTask({ ...task, title })

    const saveTask = async () => {
      try {
        setTask(await taskRepo.save(task))
      } catch (error: unknown) {
        alert((error as { message: string }).message)
      }
    }

    const deleteTask = async () => {
      try {
        await taskRepo.delete(task)
        setTasks(tasks.filter(t => t !== task))
      } catch (error: unknown) {
        alert((error as { message: string }).message)
      }
    }

    return (
      <div key={task.id}>
        <input
          type="checkbox"
          checked={task.completed}
          onChange={e => setCompleted(e.target.checked)}
        />
        <input value={task.title} onChange={e => setTitle(e.target.value)} />
        <button onClick={saveTask}>Save</button>
        <button onClick={deleteTask}>Delete</button>
      </div>
    )
  })
}

MIT Licensed | Made by the Remult team with ❤️