-
Notifications
You must be signed in to change notification settings - Fork 332
LLM:Typescript
This page aims to explain how to properly type some tricky parts of the LLM codebase.
First of all in order to understand the basics, you should definitely check the documentation which contains a lot of useful information.
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 ofnavigate(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.
We wrote multiple helper types located in src/components/RootNavigator/types/helpers.ts
to ease the typing of screens.
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>
>
>;
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 toStackNavigatorProps<…>["navigation"]
.
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 toStackNavigatorProps<…>["route"]
.
Typing redux related code is split in two categories: states, actions, and reducers
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
}
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.
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;
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();
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);
💡 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
andreducers/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,
});
- Ledger Live Desktop
- Ledger Live Mobile
-
Ledger Live Common
- Introduction
- Currency Models
- Currency Bridge
- Account
- Account Bridge
- apps
- appsCheckAllAppVersions
- ledger-live bot
- Canonical Ways to Investigate Bugs
- Coin Integration Introduction
- Countervalues
- Packages Duplicates
- Derivation
- Developing with CLI
- Developing
- Gist Firmware Update
- Gist Transaction
- Hardware Wallet Logic
- Socket
- Assorted tips
- Integration Tests
- Process
- Monorepository Migration Guide
- Issues, Workaround and Tricks
- Common CI Troubleshooting
- Create staging builds using the CI
- Deprecated