Code for Life Community hub
Python DenRapid RouterCode for Life website
  • 💛Welcome
  • 📅Release Notes
  • âĪïļBecome a Contributor
  • ðŸŠīIndustry Experience
  • 🏆Wall of Fame
  • ðŸ“ĶRepositories and Packages
  • ðŸī󠁧ó Ēó Ĩó Ū󠁧ó ŋNational Curriculum alignment
  • ðŸī󠁧ó Ēó ģó Ģó īó ŋNational Curriculum alignment
  • â„đïļGlossary
  • COMMUNICATIONS
    • 📖Publications
  • 🗞ïļNewsletters
    • 🎊Spring 2025
    • 🌷Bett show 2025
    • 🎄December 2024
    • 🍁Autumn 2024
    • ðŸŒŧSummer 2024
    • 🐰Spring 2024
    • ❄ïļWinter 2024
    • 🎄December 2023
    • 🍁Autumn 2023
    • ðŸŒŧSummer 2023
    • 🌷Spring 2023
    • 🎄December 2022
    • 🍁Autumn 2022
    • ðŸŒŧSummer 2022
    • 🌷Spring 2022
    • 🎄December 2021
  • ðŸ§ĐNewsletter challenges
    • ðŸĪ–Ocado Robot Debugging Challenge!
  • ðŸĪ–Books of Robots
  • Community resource hub
    • ðŸ’ŧHow do Computers work?
    • 🔐Safety online: Passwords and Security
    • ðŸĪ–The World of Robotics
    • ðŸĶūCareers in technology
      • 📃Careers posters
      • ðŸ“―ïļCareer based videos
  • ðŸ§ĐBlockly to Python Guide
  • Python Commands
  • 🗚ïļLevel Maps 1‒50
  • 🗚ïļLevel maps 51–67
  • Software Developer Guide
    • ðŸ’ŧDev Environment Setup
    • 🔝Code Promotion Strategy
    • ðŸ’ŋBack End
      • â„đïļOverview
      • ✍ïļCoding Patterns
      • 🧊Testing Strategy
    • ðŸ–ąïļFront End
      • â„đïļOverview
      • ✍ïļCoding Patterns
      • 🧊Testing Strategy
  • Links
    • Code Workspace
    • Visit our Site
  • Rapid Router Repo
Powered by GitBook
LogoLogo

Copyright Ocado Group 2025

On this page
  • API
  • Defining Models
  • Picking Model Fields
  • Defining Results & Args
  • Defining URLs
  • Defining Endpoints
  • Defining a Retrieve Endpoint
  • Defining a List Endpoint
  • Defining a Create Endpoint
  • Defining a Update Endpoint
  • Defining a Destroy Endpoint
  • Forms
  • Defining Fields
  • Submitting a Form
  • Schemas
  • Defining a Schema
  • Components
  • Defining a Component
  • Reusing Components
  • Pages
  • Defining Routes
  • Navigating to Routes
  • Link Components
  • Theme
  • Extending the Theme
  • Environment Variables
  • Setting Env. Variables
  • Importing Env. Variables

Was this helpful?

Export as PDF
  1. Software Developer Guide
  2. Front End

Coding Patterns

Coding patterns we follow on our front end

PreviousOverviewNextTesting Strategy

Last updated 8 months ago

Was this helpful?

API

We're using (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.

api/person.ts
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
    }
>

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

api/index.ts
const api = createApi({
    tagTypes: ["Person"],
})

Picking Model Fields

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

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.

api/person.ts
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
}
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
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.

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

Defining URLs

Documentation coming soon

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.

api/person.ts
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

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.

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.

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.

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.

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.

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

Documentation coming soon

Defining Fields

Documentation coming soon

Submitting a Form

Documentation coming soon

Schemas

Documentation coming soon

Defining a Schema

Documentation coming soon

Components

Documentation coming soon

Defining a Component

Documentation coming soon

Reusing Components

Documentation coming soon

Pages

Documentation coming soon

Defining Routes

Documentation coming soon

Navigating to Routes

Documentation coming soon

Link Components

Documentation coming soon

Theme

Documentation coming soon

Extending the Theme

Documentation coming soon

Environment Variables

Documentation coming soon

Setting Env. Variables

Documentation coming soon

Importing Env. Variables

Documentation coming soon

If the fields of the result or arg are fields of the model, use the Result and Arg type constructors. These function similarly to .

If the result and arg are for a , use the default type helper for the specific action. These function similarly to .

ðŸ–ąïļ
✍ïļ
Redux Toolkit Query
Pick
default action on the model-view-set
Pick