/
Typescript and React-Query for api usage.

Typescript and React-Query for api usage.

Objective

The purpose of this document/presentation is to lay out a usage pattern for using React-Query and Typescript (TS) to wrap API usage in a react app. This pattern explicitly deals with updating only the API code to typescript, while leaving the rest as generic JavaScript.

Why limit typescript to API code?

The goal of This pattern is to provide a light-weight entry-point for an app to include Typescript where it will provide the largest value.

In general terms, one can break React code into 3 categories:

  • Presentational logic/components

  • Internal/local business logic

  • business logic around external API data

Of these categories, Presentational Components come bundled in React with prop-types, which provides type-checking for those components.

Internal business logic is actually a pretty valuable place to wrap TS as well, but is more likely to be more tightly intwined with the internals of an already-crafted app, and thus a harder starting point.

However, external API data logic is referencing shapes that are otherwise not (necessarily) locally referenced, and as this data should be fairly static, it should be much easier to update in isolation. Adding Typescript around this code provides build-time validation that consumers of the API are consuming it appropriately.

Why react-query?

https://tanstack.com/query/latest/docs/react/overview

As we are wrapping our API data in Typescript, we are also taking steps to separate data that is static (from an external source) from data that is directly tied to the UI logic. react-query provides a simple and clean wrapper for API events, while also separating out this static API data fetching, storage, and access from redux.

Caveats

Caveat 1 - Aimed at edx MFE packages

This document is aimed specifically at consumers of the edx build toolchain, based on @edx/frontend-build. This package has an alpha branch with Typescript support and dependencies, including updating a babel build. If you are not consuming the edx toolchain for your build process, you will need to install Typescript to go into your linting/babel system appropriately.

Caveat 2 - Alpha branch

The consuming repos so far require a --legacy-peer-deps argument for npm install and npm ci calls while supporting this branch, as there are not matching versions in the other packages in our tool-chain. Unfortunately, this does leave us open to hiding and not fixing other peer dependency issues. We are working as quickly as possible to get that alpha branch merged into master to remove this requirement.

Note: @edx/react-unit-test-utils

This presentation will make use of the @edx/react-unit-test-utils library for a number of utilities.

Most notably, you wills see a StrictDict referenced, that is just a wrapper for objects that complains when called with an invalid/missing key. These are useful for key-stores for testing and validation.

Typescript App Setup

  • Install the alpha branch of frontend build

    • npm install @edx/frontend-build@alpha

  • Add a tsconfig.json file to the top-level of your app

    • { "extends": "@edx/typescript-config", "compilerOptions": { "rootDir": ".", "outDir": "dist", "baseUrl": "./src", "paths": { "*": ["*"] } }, "include": ["src/**/*"], "exclude": ["dist", "node_modules"] }
  • Update commitlint job (.github/workflows/commitlint.yml) to remove the tsconfig when running

    • See issue https://github.com/conventional-changelog/commitlint/issues/3256

    • # Run commitlint on the commit messages in a pull request. name: Lint Commit Messages on: - pull_request jobs: commitlint: runs-on: ubuntu-20.04 steps: - name: Check out repository uses: actions/checkout@v3 with: fetch-depth: 0 - name: remove tsconfig.json # see issue https://github.com/conventional-changelog/commitlint/issues/3256 run: | rm -f tsconfig.json - name: Check commits uses: wagoid/commitlint-github-action@v5

Typescript-ifying Your API

AKA the Fun Part

Step 1: API Design (list and define data shapes for requests/events)

Come up with a list of unique fetch events you want and give them each a unique identifier.

// .../api-src/constants.js import { StrictDict } from '@edx/react-unit-test-utils' const queryKeys = StrictDict({ userData: 'userData', systemData: 'systemData', }); export default { queryKeys };

Then, based on the API definitions, write Typescript definitions for the response types.

Note: I am not writing example definitions here as those will heavily depend on your specific api shape.

Tip: Where possible, try to break into sub-interfaces for easier typing of UI selectors later.