Skip to content

LLM:Typescript

Julien Elbaz edited this page Oct 25, 2022 · 5 revisions

Typescript Guide

This page aims to explain how to properly type some tricky parts of the LLM codebase.

⛵️ react-navigation

Official Documentation

First of all in order to understand the basics, you should definitely check the documentation which contains a lot of useful information.

Typing the navigators

Each navigator needs to be typed with a list of routes mapped to their corresponding params.

These navigator types are defined in the src/components/RootNavigator/types folder, in a .ts file having the the same name as the .tsx implementation.

Example:

import { ScreenName } from "../../../const";

export type MyNavigator = {
  [ScreenName.MyScreen]: {
    stuff: string;
    isStuffed?: boolean;
  };
};

💡 Tip: Having undefined params - or as a type union - allows to navigate to a screen or navigator without the need to pass a second argument to the call: navigate(ScreenName.…) instead of navigate(ScreenName.…, {}).

We use enums instead of strings to declare navigator and screen names. They are located in the src/const/navigation.ts file.

It seems to be obvious but as a reminder always use ScreenName for screens and NavigatorName for navigators.

Typing the screens

Helpers

We wrote multiple helper types located in src/components/RootNavigator/types/helpers.ts to ease the typing of screens.

Props

To properly type the props of a Screen you should use the StackNavigatorProps type most of the time.

type Props = StackNavigatorProps<
  // The navigator name which contains the screen
  OnboardingNavigatorParamList,
  // The name of the route pointing to this screen
  ScreenName.OnboardingModalWarning
>

Sometimes when the component tries to access parent navigators or screens you will have to type the full navigation stack. In this case you should use the CompositeScreenProps types (or BaseComposite / RootComposite helpers).

/*
The stack here is the following:
  - RootNavigator (covered by BaseComposite)
  - BaseNavigator (covered by BaseComposite)
  - ManagerNavigatorStackParamList
  - MainNavigatorParamList <-- we are here
*/
type Props = BaseComposite<
  CompositeScreenProps<
    StackNavigatorProps<ManagerNavigatorStackParamList>,
    StackNavigatorProps<MainNavigatorParamList>
  >
>;

useNavigation

The useNavigation hook should be typed the same way as the Props type but using specific helpers: StackNavigatorNavigation, CompositeNavigation or BaseCompositeNavigation.

const navigation = useNavigation<StackNavigatorNavigation<BaseNavigatorStackParamList>>();

💡 Tip: StackNavigatorNavigation<…> is equivalent to StackNavigatorProps<…>["navigation"].

useRoute

The useNavigation hook should be typed the same way as the Props type but using the specific StackNavigatorRoute helper StackNavigatorRoute. For composition, use the same helpers as for Props and access the ["route"] property.

const route = useRoute<StackNavigatorRoute<BaseNavigatorStackParamList>>();

💡 Tip: StackNavigatorRoute<…> is equivalent to StackNavigatorProps<…>["route"].

💾 react-redux

Typing redux related code is split in two categories: states, actions, and reducers

State

State defined the shape that the reducers will accept and need to return, as well as type the initial state of each reducer. All of these are defined in src/reducers/types.ts.

// src/reducers/types.ts

// === NAME OF STATE ===
export type MyState = {
  accounts: Account[];
  active?: string; // accountId
}

Typing the Actions

Actions are pretty straight forward to type, you can use the helpers provided by react-redux to do that. In the end, an action is just a object with a type and eventually a payload key.

Payloads and Action Types

Usually payload are defined at the root of the actions/ folder, in the types.ts file. In this file we define both the action types (as enum) and the different payloads.

// src/actions/types.ts

import type { Action } from "redux-actions";
import type { SomeExampleType } from "your-libraries";
import type { MyState } from "../reuducers/types.ts";

// We define the actions types
export enum MyStateActionTypes {
  SET_ACCOUNTS = "SET_ACCOUNTS",
  SET_ACTIVE = "SET_ACTIVE",
  REMOVE_ACTIVE = "REMOVE_ACTIVE"
};

// We define the actions payload
export type MyStateSetAccountsPayload = { accounts: MyState["accounts"] } // you could also use the type directly { accounts: Account[] }
export type MyStateSetActivePayload = { accoundId: string }
// REMOVE_ACTIVE does not require a payload
export type MyStatePayload = 
  | MyStateSetAccountsPayload
  | MyStateSetActivePayload;

Actions

To type actions, we will use the Action type helper and createAction from redux-actions (as it will abstract the payload key for us and keep a consistant coding style)

// src/actions/mystate.ts

import type { Action } from "redux-actions";
import { createAction } from "redux-actions";
import type { MyStateActionTypes, MyStateSetAccountsPayload, MyStateSetActivePayload } from "./types";

// The parametric type of createAction is the expected payload, and the argument of the function is the expected type
// It returns a new function that will accept a payload, which will return the object passed to the reducer
// In this case:
// { type: MyStateActionTypes.SET_ACCOUNTS, payload: MyStateSetAccountsPayload }
const setAccountsAction = createAction<MyStateSetAccountsPayload>(MyStateActionTypes.SET_ACCOUNTS);

// Then we use this to export our action
export const setAccoutns = (accounts: AccountsRaw[]) => {
  // do some logic to go from AccountRaw[] to Account[] (the expected payload)
  const result = accounts.map( /* ... */ );
  return setAccountsAtion(results);
}

const setActiveAction = createAction<MyStateSetActivePayload>(MyStateActionTypes.SET_ACTIVE);
export const setActive = (accountId: string) => setActiveAction(accountId);

// Example for an action with no payload
const removeActiveAction = createAction(MyStateActionTypes.REMOVE_ACTIVE);

export const removeActive = () => removeActiveAction();

Reducers

Now that we have all the elements to start typing the reducer. As we use handleActions from redux-actions, here is how to type it properly.

// src/reducers/myState.ts

import { handleActions } from "redux-actions";
import type { Action, ReducerMap } from "redux-actions";
import type { MyState } from "./types";
import type {
  MyStateActionTypes,
  MyStatePayload,
  MyStateSetAccountsPayload,
  MyStateSetActivePayload
} from "../actions/types";

export const INITIAL_STATE: MyState = {
  accounts: [],
  active: undefined
};

const handlers: ReducerMap<MyState, MyStatePayload> = {
  /* To define a reducer for an action we use
    - the action type as key
    - a function that accepts the current state (MyState) and the action
    - cast the action to the correct type as it cannot extract typings from a union
    Then we can return the new state
  */ 
  [MyStateActionTypes.SET_ACCOUNTS]: (state, action) => ({
    ...state,
    accounts: (action as Action<MyStateSetAccountsPayload>).payload.accounts
  }),
  [MyStateActionTypes.SET_ACTIVE]: (state, action) => ({
    ...state,
    active: (action as Action<MyStateSetActivePayload>).payload.active
  }),
  [MyStateActionTypes.REMOVE_ACTIVE]: (state) => ({
    ...state,
    active: undefined
  })
}

export default handleActions<MyState, MyStatePayload>(handlers, INITIAL_STATE);

Tips

💡 If a new reducers is added, don't forget to add it to the default reducer and update the types, respectively in reducers/index.ts and reducers/types.ts.

// src/reducers/types.ts

// ...
// ...
// way down in the file

// === ROOT STATE ===

export type State = {
  accounts: AccountsState;
  settings: SettingsState;
  appstate: AppState;
  ble: BleState;
  ratings: RatingsState;
  notifications: NotificationsState;
  swap: SwapStateType;
  walletconnect: WalletConnectState;
  postOnboarding: PostOnboardingState;
  // new state
  myState: MyState;
};
// src/reducers/index.ts

// ...
// ...
// previous imports
import myState from "./myState";

export type AppStore = Store<State>;

const appReducer = combineReducers({
  accounts,
  settings,
  appstate,
  ble,
  ratings,
  notifications,
  swap,
  walletconnect,
  postOnboarding,
  // new state
  myState,
});
Clone this wiki locally