wasp/web/docs/tutorials/todo-app/listing-tasks.md

4.0 KiB

title
Listing tasks

import useBaseUrl from '@docusaurus/useBaseUrl';

We want to admire our tasks, so let's list them!

Introducing operations (queries and actions)

The primary way of interacting with entities in Wasp is via operations (queries and actions).

Queries are here when we need to fetch/read something, while actions are here when we need to change/update something. We will start with writing a query, since we are just listing tasks and not modifying anything for now.

To list tasks, we will need two things:

  1. Wasp query that fetches all the tasks from the database.
  2. React logic that calls our query and displays its results.

Wasp query

Let's implement getTasks query. It consists of a declaration in Wasp and implementation in JS (in ext/ directory).

Wasp declaration

Add the following code to main.wasp:

// ...

query getTasks {
  // We specify that JS implementation of the query (which is an async JS function)
  // can be found in `ext/queries.js` as named export `getTasks`.
  fn: import { getTasks } from "@ext/queries.js",
  // We tell Wasp that this query is doing something with entity `Task`. With that, Wasp will
  // automatically refresh the results of this query when tasks change.
  entities: [Task]
}

JS implementation

Next, create a new file ext/queries.js and define the JS function that we just imported in the query declaration above:

export const getTasks = async (args, context) => {
  return context.entities.Task.findMany({})
}

Query function parameters:

  • args: object, arguments the query is invoked with.
  • context: object, additional stuff provided by Wasp.

Since we declared in main.wasp that our query uses entity Task, Wasp injected Prisma client for entity Task as context.entities.Task - we used it above to fetch all the tasks from the database.

:::info Queries and actions are NodeJS functions that are executed on the server. :::

Invoking query from React

Finally, let's use the query we just created, getTasks, in our React component to list the tasks:

import React from 'react'

import getTasks from '@wasp/queries/getTasks'
import { useQuery } from '@wasp/queries'

const MainPage = () => {
  const { data: tasks, isFetching, error } = useQuery(getTasks)

  return (
    <div>
      {tasks && <TasksList tasks={tasks} />}

      {isFetching && 'Fetching...'}
      {error && 'Error: ' + error}
    </div>
  )
}

const Task = (props) => (
  <div>
    <input
      type='checkbox' id={props.task.id}
      checked={props.task.isDone} readonly
    />
    {props.task.description}
  </div>
)

const TasksList = (props) => {
  if (!props.tasks?.length) return 'No tasks'
  return props.tasks.map((task, idx) => <Task task={task} key={idx} />)
}

export default MainPage

All of this is just regular React, except for the two special @wasp imports:

  • import getTasks from '@wasp/queries/getTasks': provides us with our freshly defined Wasp query.
  • import { useQuery } from '@wasp/queries': provides us with Wasp's useQuery React hook which is actually just a thin wrapper over react-query useQuery hook, behaving very similarly while offering some extra integration with Wasp.

While we could call query directly as getTasks(), calling it as useQuery(getTasks) gives us the reactivity (React component gets re-rendered if result of the query changes).

With these changes, you should be seeing text "No tasks" on the screen:

<img alt="Todo App - No Tasks" src={useBaseUrl('img/todo-app-no-tasks.png')} style={{ border: "1px solid black" }} />

Next, let's create some tasks!