# Coding Patterns

## API

We're using [Redux Toolkit Query](https://redux-toolkit.js.org/rtk-query/overview) (RTK Query) to define our API endpoints and cache the response data.

### Defining Models

For each model defined on the back end, create a file named after the model in the directory `api`. At the top of the file, define the model on a global level and export it. Use the `Model` type constructor.

{% code title="api/person.ts" %}

```typescript
import { type Model } from "codeforlife/utils/api"

export type Person = Model<
    number, // the type of the "id" field
    {
        first_name: string
        last_name: string
        age: number
    }
>
```

{% endcode %}

In addition, the name of each model will need to be registered as a tag in `tagTypes`, at `api/index.ts`.

{% code title="api/index.ts" %}

```typescript
const api = createApi({
    tagTypes: ["Person"],
})
```

{% endcode %}

### Picking Model Fields

If you'd like to define an object containing a subset of a model's fields, you can use `Pick`.

```typescript
const person: Pick<Person, "id" | "age"> = { id: 1, age: 29 }
```

### Defining Results & Args

When defining the result (i.e. response body) and arg (i.e. request body) for an endpoint, you must define their types as a pair on a global level below the model's type and export them. The result's type should come first, immediately followed by the arg's type. The result & arg types should follow the naming convention `{endpoint_name}Result` and `{endpoint_name}Arg`. Each result & arg pair should be separated with one newline.

{% code title="api/person.ts" %}

```typescript
export type Person = Model<...>

// For endpoint "updatePersonName"
export type UpdatePersonNameResult = { id: number }
export type UpdatePersonNameArg = {
    id: number
    first_name: string
    last_name: string
}

// For endpoint "updatePersonAge"
export type UpdatePersonAgeResult = { id: number }
export type UpdatePersonAgeArg = {
    id: number
    age: number
}
```

{% endcode %}

If the fields of the result or arg are fields of the model, use the `Result` and `Arg` type constructors. These function similarly to [`Pick`](#picking-model-fields).

```typescript
import { type Arg, type Result } from "codeforlife/utils/api"

export type UpdatePersonNameResult = Result<Person>
export type UpdatePersonNameArg = Arg<
  Person, // model
  never, // required fields.
  "first_name" | "last_name" // optional fields
> & Pick<Person, "id"> // "id" cannot be a required or optional field
```

If the result and arg are for a [default action on the model-view-set](https://www.django-rest-framework.org/api-guide/viewsets/#viewset-actions), use the default type helper for the specific action. These function similarly to [`Pick`](#picking-model-fields).

```typescript
import { type UpdateArg, type UpdateResult } from "codeforlife/utils/api"

export type UpdatePersonResult = UpdateResult<Person>
export type UpdatePersonArg = UpdateArg<
  Person, // model
  never, // required fields
  "first_name" | "last_name" | "age" // optional fields
> // "id" is picked by default
```

If no data is included in the result or arg, set either to `null`.&#x20;

```typescript
export type DeletePersonResult = null
export type DeletePersonArg = Person["id"]
```

### Defining URLs

<mark style="color:red;">Documentation coming soon</mark>

### Defining Endpoints

Endpoints should be defined below the result & arg pairs. Endpoints should be injected into the base API, imported from `api/index.ts`.  The subset of API endpoints should follow the naming convention `{model_name}Api` and be exported as default. In addition, each hook auto-generated from the endpoints should be unpacked and exported as a constant below the default export.

{% code title="api/person.ts" %}

```typescript
import api from "."

export type Person = Model<...>

export type UpdatePersonResult = UpdateResult<Person>
export type UpdatePersonArg = UpdateArg<Person, ...>

const personApi = api.injectEndpoints({
    endpoints: build => ({
        updatePerson: build.mutation<UpdatePersonResult, UpdatePersonArg>({
            query: ({ id, ...body }) => ({
                url: `persons/${id}/`,
                method: "PATCH",
                body,
            })
        })
    })
})

export default personApi
export const { useUpdatePersonMutation } = personApi
```

{% endcode %}

### Defining a Retrieve Endpoint

When defining a retrieve endpoint, it's recommended to use the `RetrieveResult` and `RetrieveArg` type constructors. The endpoint should follow the naming convention `retrieve{model_name}`, be defined as a `query`, use the method `"GET"`, and name the query's arg as `id`. To build the URL, the `buildUrl` utility should be called, providing the model's detail URL and id as a URL parameter. The tag should be provided by calling the `tagData` utility and providing the model's name as an argument.

```typescript
import {
    type RetrieveArg,
    type RetrieveResult,
    buildUrl,
    tagData,
} from "codeforlife/utils/api"

export type RetrievePersonResult = RetrieveResult<Person, ...>
export type RetrievePersonArg = RetrieveArg<Person>

const personApi = api.injectEndpoints({
    endpoints: build => ({
        retrievePerson: build.query<RetrievePersonResult, RetrievePersonArg>({
            query: id => ({
                url: buildUrl(personUrls.detail, { url: { id } }),
                method: "GET",
            }),
            providesTags: tagData("Person")
        })
    })
})
```

### Defining a List Endpoint

When defining a list endpoint, it's recommended to use the `ListResult` and `ListArg` type constructors. The endpoint should follow the naming convention `list{model_name_plural}`, be defined as a `query`, use the method `"GET"`, and name the query's arg as `search`. To build the URL, the `buildUrl` utility should be called, providing the model's list URL and the search parameters. The tags should be provided by calling the `tagData` utility and providing the model's name as an argument and setting `includeListTag` to true.

```typescript
import {
    type ListArg,
    type ListResult,
    buildUrl,
    tagData,
} from "codeforlife/utils/api"

export type ListPersonsResult = ListResult<Person, ...>
export type ListPersonsArg = ListArg<...>

const personApi = api.injectEndpoints({
    endpoints: build => ({
        listPersons: build.query<ListPersonsResult, ListPersonsArg>({
            query: search => ({
                url: buildUrl(personUrls.list, { search }),
                method: "GET",
            }),
            providesTags: tagData("Person", { includeListTag: true })
        })
    })
})
```

### Defining a Create Endpoint

When defining a create endpoint, it's recommended to use the `CreateResult` and `CreateArg` type constructors. The endpoint should follow the naming convention `create{model_name}`, be defined as a `mutation`, use the method `"POST"`, and name the query's arg as `body`. The URL should be set as the model's list URL. The tags should be invalidated by calling the `tagData` utility and providing the model's name as an argument and setting `includeListTag` to true.

```typescript
import {
    type CreateArg,
    type CreateResult,
    tagData,
} from "codeforlife/utils/api"

export type CreatePersonResult = CreateResult<Person, ...>
export type CreatePersonArg = CreateArg<Person, ...>

const personApi = api.injectEndpoints({
    endpoints: build => ({
        createPerson: build.mutation<CreatePersonResult, CreatePersonArg>({
            query: body => ({
                url: personUrls.list,
                method: "POST",
                body,
            }),
            invalidatesTags: tagData("Person", { includeListTag: true })
        })
    })
})
```

### Defining a Update Endpoint

When defining a update endpoint, it's recommended to use the `UpdateResult` and `UpdateArg` type constructors. The endpoint should follow the naming convention `update{model_name}`, be defined as a `mutation`, use the method `"PATCH"`, and unpack the query's arg as `({ id, ...body })`. To build the URL, the `buildUrl` utility should be called, providing the model's detail URL and id as a URL parameter. The tags should be invalidated by calling the `tagData` utility and providing the model's name as an argument and setting `includeListTag` to true.

```typescript
import {
    type UpdateArg,
    type UpdateResult,
    buildUrl,
    tagData,
} from "codeforlife/utils/api"

export type UpdatePersonResult = UpdateResult<Person, ...>
export type UpdatePersonArg = UpdateArg<Person, ...>

const personApi = api.injectEndpoints({
    endpoints: build => ({
        updatePerson: build.mutation<UpdatePersonResult, UpdatePersonArg>({
            query: ({ id, ...body }) => ({
                url: buildUrl(personUrls.detail, { url: { id } }),
                method: "PATCH",
                body,
            }),
            invalidatesTags: tagData("Person", { includeListTag: true })
        })
    })
})
```

### Defining a Destroy Endpoint

When defining a destroy endpoint, it's recommended to use the `DestoryResult` and `DestroyArg` type constructors. The endpoint should follow the naming convention `destroy{model_name}`, be defined as a `mutation`, use the method `"DELETE"`, and name the query's arg as `id`. To build the URL, the `buildUrl` utility should be called, providing the model's detail URL and id as a URL parameter. The tags should be invalidated by calling the `tagData` utility and providing the model's name as an argument and setting `includeListTag` to true.

```typescript
import {
    type DestroyArg,
    type DestroyResult,
    buildUrl,
    tagData,
} from "codeforlife/utils/api"

export type DestroyPersonResult = DestroyResult
export type DestroyPersonArg = DestroyArg<Person>

const personApi = api.injectEndpoints({
    endpoints: build => ({
        destroyPerson: build.mutation<DestroyPersonResult, DestroyPersonArg>({
            query: id => ({
                url: buildUrl(personUrls.detail, { url: { id } }),
                method: "DELETE",
            }),
            invalidatesTags: tagData("Person", { includeListTag: true })
        })
    })
})
```

## Forms

<mark style="color:red;">Documentation coming soon</mark>

### Defining Fields

<mark style="color:red;">Documentation coming soon</mark>

### Submitting a Form

<mark style="color:red;">Documentation coming soon</mark>

## Schemas

<mark style="color:red;">Documentation coming soon</mark>

### Defining a Schema

<mark style="color:red;">Documentation coming soon</mark>

## Components

<mark style="color:red;">Documentation coming soon</mark>

### Defining a Component

<mark style="color:red;">Documentation coming soon</mark>

### Reusing Components

<mark style="color:red;">Documentation coming soon</mark>

## Pages

<mark style="color:red;">Documentation coming soon</mark>

### Defining Routes

<mark style="color:red;">Documentation coming soon</mark>

### Navigating to Routes

<mark style="color:red;">Documentation coming soon</mark>

### Link Components

<mark style="color:red;">Documentation coming soon</mark>

## Theme

<mark style="color:red;">Documentation coming soon</mark>

### Extending the Theme

<mark style="color:red;">Documentation coming soon</mark>

## Environment Variables

<mark style="color:red;">Documentation coming soon</mark>

### Setting Env. Variables

<mark style="color:red;">Documentation coming soon</mark>

### Importing Env. Variables

<mark style="color:red;">Documentation coming soon</mark>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.codeforlife.education/software-developer-guide/front-end/coding-patterns.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
