mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-11-23 19:29:17 +03:00
Update full-stack type safety docs (#1126)
This commit is contained in:
parent
4171f5608b
commit
4ad22ffc9d
@ -11,8 +11,9 @@ This document assumes you are familiar with TypeScript and primarily focuses on
|
|||||||
The document also assumes a basic understanding of core Wasp features (e.g., Queries, Actions, Entities). You can read more about these features in [our feature docs](https://wasp-lang.dev/docs/language/features).
|
The document also assumes a basic understanding of core Wasp features (e.g., Queries, Actions, Entities). You can read more about these features in [our feature docs](https://wasp-lang.dev/docs/language/features).
|
||||||
|
|
||||||
Besides allowing you to write your code in TypeScript, Wasp also supports:
|
Besides allowing you to write your code in TypeScript, Wasp also supports:
|
||||||
|
|
||||||
- Importing and using Wasp Entity types (on both the server and the client).
|
- Importing and using Wasp Entity types (on both the server and the client).
|
||||||
- Automatically generated types for Queries and Actions.
|
- Automatic full-stack type support for Queries and Actions - frontend types are automatically inferred from backend definitions.
|
||||||
- Type-safe generic hooks (`useQuery` and `useAction`) with the accompanying type inference.
|
- Type-safe generic hooks (`useQuery` and `useAction`) with the accompanying type inference.
|
||||||
- Type-safe optimistic update definitions.
|
- Type-safe optimistic update definitions.
|
||||||
|
|
||||||
@ -25,12 +26,15 @@ Your editor may sometimes report type and import errors even while `wasp start`
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
## Migrating your project to TypeScript
|
## Migrating your project to TypeScript
|
||||||
|
|
||||||
Wasp supports TypeScript out of the box!
|
Wasp supports TypeScript out of the box!
|
||||||
|
|
||||||
Our scaffolding already includes TypeScript, so migrating your project to TypeScript is as simple as changing file extensions and using the language. This approach allows you to gradually migrate your project on a file-by-file basis.
|
Our scaffolding already includes TypeScript, so migrating your project to TypeScript is as simple as changing file extensions and using the language. This approach allows you to gradually migrate your project on a file-by-file basis.
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
Let's first assume your Wasp file contains the following definitions:
|
Let's first assume your Wasp file contains the following definitions:
|
||||||
|
|
||||||
```c title=main.wasp
|
```c title=main.wasp
|
||||||
entity Task {=psl
|
entity Task {=psl
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
@ -43,9 +47,10 @@ query getTaskInfo {
|
|||||||
entities: [Task]
|
entities: [Task]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's now assume that your `queries.js` file looks something like this:
|
Let's now assume that your `queries.js` file looks something like this:
|
||||||
|
|
||||||
```javascript title="queries.js"
|
```javascript title="src/server/queries.js"
|
||||||
import HttpError from "@wasp/core/HttpError.js"
|
import HttpError from "@wasp/core/HttpError.js"
|
||||||
|
|
||||||
function getInfoMessage(task) {
|
function getInfoMessage(task) {
|
||||||
@ -63,15 +68,20 @@ export const getTaskInfo = async ({ id }, context) => {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
To migrate this file to TypeScript, all you have to do is:
|
To migrate this file to TypeScript, all you have to do is:
|
||||||
|
|
||||||
1. Change the filename from `queries.js` to `queries.ts`.
|
1. Change the filename from `queries.js` to `queries.ts`.
|
||||||
2. Write some types.
|
2. Write some types.
|
||||||
|
|
||||||
Let's start by only providing a basic `getInfoMessage` function. We'll see how to properly type the rest of the file in the following sections.
|
Let's start by only providing a basic `getInfoMessage` function. We'll see how to properly type the rest of the file in the following sections.
|
||||||
```typescript title=queries.ts
|
|
||||||
|
```typescript title=src/server/queries.ts
|
||||||
import HttpError from "@wasp/core/HttpError.js"
|
import HttpError from "@wasp/core/HttpError.js"
|
||||||
|
|
||||||
// highlight-next-line
|
// highlight-next-line
|
||||||
function getInfoMessage(task: { isDone: boolean, description: string }): string {
|
function getInfoMessage(task: {
|
||||||
|
isDone: boolean
|
||||||
|
description: string
|
||||||
|
}): string {
|
||||||
const isDoneText = task.isDone ? "is done" : "is not done"
|
const isDoneText = task.isDone ? "is done" : "is not done"
|
||||||
return `Task '${task.description}' is ${isDoneText}.`
|
return `Task '${task.description}' is ${isDoneText}.`
|
||||||
}
|
}
|
||||||
@ -85,10 +95,14 @@ export const getTaskInfo = async ({ id }, context) => {
|
|||||||
return getInfoMessage(task)
|
return getInfoMessage(task)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You don't need to change anything inside the `.wasp` file.
|
You don't need to change anything inside the `.wasp` file.
|
||||||
:::caution
|
:::caution
|
||||||
|
|
||||||
<!-- This block is mostly duplicated in 03-listing-tasks.md -->
|
<!-- This block is mostly duplicated in 03-listing-tasks.md -->
|
||||||
|
|
||||||
Even when you use TypeScript, and your file is called `queries.ts`, you still need to import it using the `.js` extension:
|
Even when you use TypeScript, and your file is called `queries.ts`, you still need to import it using the `.js` extension:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
query getTaskInfo {
|
query getTaskInfo {
|
||||||
fn: import { getTaskInfo } from "@server/queries.js",
|
fn: import { getTaskInfo } from "@server/queries.js",
|
||||||
@ -102,9 +116,10 @@ Read more about ES modules in TypeScript [here](https://www.typescriptlang.org/d
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
## Entity Types
|
## Entity Types
|
||||||
|
|
||||||
Instead of manually specifying the types for `isDone` and `description`, we can get them from the `Task` entity type. Wasp will generate types for all entities and let you import them from `"@wasp/entities"`:
|
Instead of manually specifying the types for `isDone` and `description`, we can get them from the `Task` entity type. Wasp will generate types for all entities and let you import them from `"@wasp/entities"`:
|
||||||
|
|
||||||
```typescript title=queries.ts
|
```typescript title=src/server/queries.ts
|
||||||
import HttpError from "@wasp/core/HttpError.js"
|
import HttpError from "@wasp/core/HttpError.js"
|
||||||
// highlight-next-line
|
// highlight-next-line
|
||||||
import { Task } from "@wasp/entities"
|
import { Task } from "@wasp/entities"
|
||||||
@ -124,12 +139,14 @@ export const getTaskInfo = async ({ id }, context) => {
|
|||||||
return getInfoMessage(task)
|
return getInfoMessage(task)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
By doing this, we've connected the argument type of the `getInfoMessage` function with the `Task` entity. This coupling removes duplication and ensures the function keeps the correct signature even if we change the entity. Of course, the function might throw type errors depending on how we change the entity, but that's precisely what we want!
|
By doing this, we've connected the argument type of the `getInfoMessage` function with the `Task` entity. This coupling removes duplication and ensures the function keeps the correct signature even if we change the entity. Of course, the function might throw type errors depending on how we change the entity, but that's precisely what we want!
|
||||||
|
|
||||||
Don't worry about typing the query function for now. We'll see how to handle this in the next section.
|
Don't worry about typing the query function for now. We'll see how to handle this in the next section.
|
||||||
|
|
||||||
Entity types are also available on the client under the same import:
|
Entity types are also available on the client under the same import:
|
||||||
```tsx title=Main.jsx
|
|
||||||
|
```tsx title=src/client/Main.jsx
|
||||||
import { Task } from "@wasp/entities"
|
import { Task } from "@wasp/entities"
|
||||||
|
|
||||||
export function ExamplePage() {}
|
export function ExamplePage() {}
|
||||||
@ -138,15 +155,17 @@ export function ExamplePage() {}
|
|||||||
description: "Some random task",
|
description: "Some random task",
|
||||||
isDone: false,
|
isDone: false,
|
||||||
}
|
}
|
||||||
return <div>{task.description}</div>;
|
return <div>{task.description}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
The mentioned type safety mechanisms also apply here: Changing the task entity in our `.wasp` file changes the imported type, which might throw a type error and warn us that our task definition is outdated.
|
|
||||||
|
|
||||||
|
The mentioned type safety mechanisms also apply here: changing the task entity in our `.wasp` file changes the imported type, which might throw a type error and warn us that our task definition is outdated.
|
||||||
|
|
||||||
## Backend type support for Queries and Actions
|
## Backend type support for Queries and Actions
|
||||||
Wasp automatically generates the appropriate types for all operations (i.e., Actions and Queries) you define inside your `.wasp` file. Assuming your `.wasp` file contains the following definition:
|
|
||||||
|
Wasp automatically generates the appropriate types for all Operations (i.e., Actions and Queries) you define inside your `.wasp` file. Assuming your `.wasp` file contains the following definition:
|
||||||
|
|
||||||
```c title=main.wasp
|
```c title=main.wasp
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
@ -155,15 +174,17 @@ query GetTaskInfo {
|
|||||||
entities: [Task]
|
entities: [Task]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Wasp will generate a type called `GetTaskInfo`, which you can use to type the Query's implementation. By assigning the `GetTaskInfo` type to your function, you get the type information for its context. In this case, TypeScript will know the `context.entities` object must include the `Task` entity. If the Query had auth enabled, it would also know that `context` includes user information.
|
Wasp will generate a type called `GetTaskInfo`, which you can use to type the Query's implementation. By assigning the `GetTaskInfo` type to your function, you get the type information for its context. In this case, TypeScript will know the `context.entities` object must include the `Task` entity. If the Query had auth enabled, it would also know that `context` includes user information.
|
||||||
|
|
||||||
`GetTaskInfo` can is a generic type that takes two (optional) type arguments:
|
`GetTaskInfo` can is a generic type that takes two (optional) type arguments:
|
||||||
|
|
||||||
1. `Input` - The argument (i.e., payload) received by the query function.
|
1. `Input` - The argument (i.e., payload) received by the query function.
|
||||||
2. `Output` - The query function's return type.
|
2. `Output` - The query function's return type.
|
||||||
|
|
||||||
Suppose you don't care about typing the Query's inputs and outputs. In that case, you can omit both type arguments, and TypeScript will infer the most general types (i.e., `never` for the input, `unknown` for the output.).
|
Suppose you don't care about typing the Query's inputs and outputs. In that case, you can omit both type arguments, and TypeScript will infer the most general types (i.e., `never` for the input, `unknown` for the output.).
|
||||||
|
|
||||||
```typescript title=queries.ts
|
```typescript title=src/server/queries.ts
|
||||||
import HttpError from "@wasp/core/HttpError.js"
|
import HttpError from "@wasp/core/HttpError.js"
|
||||||
import { Task } from "@wasp/entities"
|
import { Task } from "@wasp/entities"
|
||||||
// highlight-next-line
|
// highlight-next-line
|
||||||
@ -194,118 +215,128 @@ export const getTaskInfo: GetTaskInfo<Pick<Task, "id">, string> = async ({ id },
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
Everything described above applies to Actions as well.
|
Everything described above applies to Actions as well.
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
If don't want to define a new type for the Query's return value, the new `satisfies` keyword will allow TypeScript to infer it automatically:
|
||||||
|
```typescript
|
||||||
|
const getFoo = ((_args, context) => {
|
||||||
|
const foos = context.entities.Foo.findMany()
|
||||||
|
return {
|
||||||
|
foos,
|
||||||
|
message: "Here are some foos!",
|
||||||
|
queriedAt: new Date(),
|
||||||
|
}
|
||||||
|
}) satisfies GetFoo
|
||||||
|
```
|
||||||
|
From the snippet above, TypeScript knows:
|
||||||
|
1. The correct type for `context`.
|
||||||
|
2. The Query's return type is `{ foos: Foo[], message: string, queriedAt: Date }`.
|
||||||
|
|
||||||
|
If you don't need the context, you can skip specifying the Query's type (and arguments):
|
||||||
|
```typescript
|
||||||
|
const getFoo = () => {{ name: 'Foo', date: new Date() }}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## Frontend type support for Queries and Actions
|
## Frontend type support for Queries and Actions
|
||||||
Wasp will soon support automatic full-stack type safety à la tRPC. Until then, you can get static type checking by manually passing type arguments to `useQuery` and `useAction` hooks.
|
|
||||||
|
|
||||||
### Type support for the `useQuery` hook
|
Wasp supports automatic full-stack type safety à la tRPC. You only need to define the Operation's type on the backend, and the frontend will automatically know how to call it.
|
||||||
To add type support to Queries on the frontend, you can use:
|
|
||||||
- Entity types imported from `"@wasp/entities"`.
|
|
||||||
- The generic hook `useQuery<Input, Output, Error>` (read more about this hook [here](/docs/language/features#the-useaction-hook)):
|
|
||||||
- `Input` - Use this type argument to specify the type for the **request's payload**.
|
|
||||||
- `Output` - Use this type argument to specify the type for the **resposne's payload**.
|
|
||||||
- `Error` - Use this type argument to specify the error the Query throws.
|
|
||||||
|
|
||||||
Here's what a component that uses the Query the `getTaskInfo` might look like:
|
### Frontend type support for Queries
|
||||||
```tsx title="TaskInfo.tsx"
|
The examples assume you've defined the Query `getTaskInfo` from the previous sections:
|
||||||
import { useQuery } from "@wasp/queries"
|
|
||||||
import getTaskInfo from "@wasp/queries/getTaskInfo"
|
|
||||||
import { Task } from "@wasp/entities"
|
|
||||||
|
|
||||||
type TaskInfoPayload = Pick<Task, "id">
|
```typescript title="src/server/queries.ts"
|
||||||
|
export const getTaskInfo: GetTaskInfo<Pick<Task, "id">, string> =
|
||||||
export const TaskInfo = () => {
|
async ({ id }, context) => {
|
||||||
const {
|
|
||||||
// TypeScript knows `taskInfo` is a `string | undefined` because of the
|
|
||||||
// second type argument.
|
|
||||||
data: taskInfo,
|
|
||||||
// TypeScript also knows `isError` is a `boolean` regardless of the
|
|
||||||
// specified type arguments.
|
|
||||||
isError,
|
|
||||||
// TypeScript knows `id` must be a `Task["id"]` (i.e., a number) because of
|
|
||||||
// the first type argument.
|
|
||||||
// highlight-next-line
|
|
||||||
} = useQuery<TaskInfoPayload, string>(getTaskInfo, { id: 1 })
|
|
||||||
|
|
||||||
if (isError) {
|
|
||||||
return <div>Error when fetching tasks</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeScript forces you to perform this check.
|
|
||||||
return taskInfo === undefined ? <div>Waiting for info...</div> : <div>{taskInfo}</div>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
The above example omits the type argument for the error because it doesn't need it. Here's an example that uses the `error`:
|
|
||||||
```tsx title="TaskInfo.tsx"
|
|
||||||
import { useQuery } from "@wasp/queries"
|
|
||||||
import getTaskInfo from "@wasp/queries/getTaskInfo"
|
|
||||||
import { Task } from "@wasp/entities"
|
|
||||||
|
|
||||||
type TaskInfoPayload = Pick<Task, "id">
|
|
||||||
// highlight-next-line
|
|
||||||
type TaskInfoError = { message: string }
|
|
||||||
|
|
||||||
export const TaskInfo = () => {
|
|
||||||
const {
|
|
||||||
data: taskInfo,
|
|
||||||
isError,
|
|
||||||
// TypeScript knows `error` is a `TaskInfoError` because of the third type
|
|
||||||
// argument.
|
|
||||||
error,
|
|
||||||
// highlight-next-line
|
|
||||||
} = useQuery<TaskInfoPayload, string, TaskInfoError>(getTaskInfo, { id: 1 })
|
|
||||||
|
|
||||||
if (isError) {
|
|
||||||
// highlight-next-line
|
|
||||||
return <div> Error during fetching tasks: {error.message || ''}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeScript forces you to perform this check.
|
|
||||||
return taskInfo === undefined ? <div>Waiting for info...</div> : <div>{taskInfo}</div>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Type support for the `useAction` hook
|
|
||||||
To add type support to Actions on the frontend, you can use:
|
|
||||||
- Entity types imported from `"@wasp/entities"`.
|
|
||||||
- The generic hook `useAction<Input, Output, Error>` (read more about this hook [here](/docs/language/features#the-useaction-hook)):
|
|
||||||
- `Input` - This type argument specifies the type for the **request's payload**.
|
|
||||||
- `Output` - This type argument specifies the type for the **response's payload**.
|
|
||||||
- `Error` - This type argument specifies the error the Query throws.
|
|
||||||
|
|
||||||
Assuming the following action definition in your `.wasp` file (and the corresponding implementation in `src/server/actions.js`):
|
|
||||||
```typescript title=main.wasp
|
|
||||||
// ...
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Wasp will use the type of `getTaskInfo` to infer the Query's types on the frontend:
|
||||||
|
|
||||||
|
```tsx title="src/client/TaskInfo.tsx"
|
||||||
|
import { useQuery } from "@wasp/queries"
|
||||||
|
// Wasp knows the type of `getTaskInfo` thanks to your backend definition.
|
||||||
|
// highlight-next-line
|
||||||
|
import getTaskInfo from "@wasp/queries/getTaskInfo"
|
||||||
|
|
||||||
|
export const TaskInfo = () => {
|
||||||
|
const {
|
||||||
|
// TypeScript knows `taskInfo` is a `string | undefined` thanks to the
|
||||||
|
// backend definition.
|
||||||
|
data: taskInfo,
|
||||||
|
// TypeScript also knows `isError` is a `boolean`.
|
||||||
|
isError,
|
||||||
|
// TypeScript knows `error` is of type `Error`.
|
||||||
|
error,
|
||||||
|
// TypeScript knows `id` must be a `Task["id"]` (i.e., a number) thanks to
|
||||||
|
// your backend definition.
|
||||||
|
// highlight-next-line
|
||||||
|
} = useQuery(getTaskInfo, { id: 1 })
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return <div> Error during fetching tasks: {error.message || "unknown"}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeScript forces you to perform this check.
|
||||||
|
return taskInfo === undefined ? (
|
||||||
|
<div>Waiting for info...</div>
|
||||||
|
) : (
|
||||||
|
<div>{taskInfo}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend type support for Actions
|
||||||
|
|
||||||
|
Assuming the following action definition in your `.wasp` file
|
||||||
|
|
||||||
|
```typescript title=main.wasp
|
||||||
action addTask {
|
action addTask {
|
||||||
fn: import { addTask } from "@server/actions.js"
|
fn: import { addTask } from "@server/actions.js"
|
||||||
entities: [Task]
|
entities: [Task]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Here's how you can use it:
|
|
||||||
```tsx title=AddTask.tsx
|
And its corresponding implementation in `src/server/actions.ts`:
|
||||||
|
|
||||||
|
```typescript title=src/server/actions.ts
|
||||||
|
import { AddTask } from "@wasp/actions/types"
|
||||||
|
|
||||||
|
type TaskPayload = Pick<Task, "description" | "isDone">
|
||||||
|
|
||||||
|
const addTask: AddTask<TaskPayload, Task> = async (args, context) => {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's how to use it on the frontend:
|
||||||
|
```tsx title=src/client/AddTask.tsx
|
||||||
import { useAction } from "@wasp/actions"
|
import { useAction } from "@wasp/actions"
|
||||||
|
// TypeScript knows `addTask` is a function that expects a value of type
|
||||||
|
// `TaskPayload` and returns a value of type `Promise<Task>`.
|
||||||
import addTask from "@wasp/queries/addTask"
|
import addTask from "@wasp/queries/addTask"
|
||||||
import { Task } from "@wasp/entities"
|
|
||||||
|
|
||||||
const AddTask = ({ description }: Pick<Task, "description">) => {
|
const AddTask = ({ description }: Pick<Task, "description">) => {
|
||||||
|
|
||||||
// TypeScript knows `addTaskAction` is a function that expects a value of
|
|
||||||
// type `Pick<Task, "description"> and returns a value of type
|
|
||||||
// `Promise<Task>`.
|
|
||||||
const addTaskAction = useAction<Pick<Task, "description" | "isDone">, Task>(addTask)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button onClick={() => addTask({ description, isDone: false })}>
|
||||||
onClick={() => addTaskAction({ description, isDone: false })}
|
Add unfinished task
|
||||||
>Add unfinished task</button>
|
</button>
|
||||||
<button
|
<button onClick={() => addTask({ description, isDone: true })}>
|
||||||
onClick={() => addTaskAction({ description, isDone: true })}
|
Add finished task
|
||||||
>Add finished task</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
#### Type support for the `useAction` hook
|
||||||
|
Type inference also works if you decide to use the action via the `useAction` hook:
|
||||||
|
```typescript
|
||||||
|
// addTaskFn is of type (args: TaskPayload) => Task
|
||||||
|
const addTaskFn = useAction(addTask)
|
||||||
```
|
```
|
||||||
|
|
||||||
The `useAction` hook also includes support for optimistic updates. Read [the feature docs](/docs/language/features#the-useaction-hook) to understand more about optimistic updates and how to define them in Wasp.
|
The `useAction` hook also includes support for optimistic updates. Read [the feature docs](/docs/language/features#the-useaction-hook) to understand more about optimistic updates and how to define them in Wasp.
|
||||||
@ -313,14 +344,16 @@ The `useAction` hook also includes support for optimistic updates. Read [the fea
|
|||||||
Here's an example that shows how you can use static type checking in their definitions (the example assumes an appropriate action defined in the `.wasp` file and implemented on the server):
|
Here's an example that shows how you can use static type checking in their definitions (the example assumes an appropriate action defined in the `.wasp` file and implemented on the server):
|
||||||
|
|
||||||
```tsx title=Task.tsx
|
```tsx title=Task.tsx
|
||||||
import { useQuery } from '@wasp/queries'
|
import { useQuery } from "@wasp/queries"
|
||||||
import { OptimisticUpdateDefinition, useAction } from '@wasp/actions'
|
import { OptimisticUpdateDefinition, useAction } from "@wasp/actions"
|
||||||
import updateTaskIsDone from '@wasp/actions/updateTaskIsDone'
|
import updateTaskIsDone from "@wasp/actions/updateTaskIsDone"
|
||||||
|
|
||||||
type TaskPayload = Pick<Task, "id" | "isDone">
|
type TaskPayload = Pick<Task, "id" | "isDone">
|
||||||
|
|
||||||
const Task = ({ taskId }: Pick<Task, "id">) => {
|
const Task = ({ taskId }: Pick<Task, "id">) => {
|
||||||
const updateTaskIsDoneOptimistically = useAction<TaskPayload, Task>(updateTaskIsDone, {
|
const updateTaskIsDoneOptimistically = useAction(
|
||||||
|
updateTaskIsDone,
|
||||||
|
{
|
||||||
optimisticUpdates: [
|
optimisticUpdates: [
|
||||||
{
|
{
|
||||||
getQuerySpecifier: () => [getTask, { id: taskId }],
|
getQuerySpecifier: () => [getTask, { id: taskId }],
|
||||||
@ -331,13 +364,15 @@ const Task = ({ taskId }: Pick<Task, "id">) => {
|
|||||||
{
|
{
|
||||||
getQuerySpecifier: () => [getTasks],
|
getQuerySpecifier: () => [getTasks],
|
||||||
updateQuery: (updatedTask, oldTasks) =>
|
updateQuery: (updatedTask, oldTasks) =>
|
||||||
oldTasks && oldTasks.map(task =>
|
oldTasks &&
|
||||||
|
oldTasks.map((task) =>
|
||||||
task.id === updatedTask.id ? { ...task, ...updatedTask } : task
|
task.id === updatedTask.id ? { ...task, ...updatedTask } : task
|
||||||
),
|
),
|
||||||
// highlight-next-line
|
// highlight-next-line
|
||||||
} as OptimisticUpdateDefinition<TaskPayload, Task[]>
|
} as OptimisticUpdateDefinition<TaskPayload, Task[]>,
|
||||||
]
|
],
|
||||||
})
|
}
|
||||||
|
)
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -345,10 +380,13 @@ const Task = ({ taskId }: Pick<Task, "id">) => {
|
|||||||
## Database seeding
|
## Database seeding
|
||||||
|
|
||||||
When implementing a seed function in TypeScript, you can import a `DbSeedFn` type via
|
When implementing a seed function in TypeScript, you can import a `DbSeedFn` type via
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import type { DbSeedFn } from '@wasp/dbSeed/types.js'
|
import type { DbSeedFn } from "@wasp/dbSeed/types.js"
|
||||||
```
|
```
|
||||||
|
|
||||||
and use it to type your seed function like this:
|
and use it to type your seed function like this:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
export const devSeedSimple: DbSeedFn = async (prismaClient) => { ... }
|
export const devSeedSimple: DbSeedFn = async (prismaClient) => { ... }
|
||||||
```
|
```
|
||||||
|
Loading…
Reference in New Issue
Block a user