Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[No QA] Add TypeScript Guideline #21549

Merged
merged 20 commits into from
Jul 24, 2023
18 changes: 18 additions & 0 deletions contributingGuides/PROPTYPES_CONVERSION_TABLE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Expensify PropTypes Conversation Table
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved

| PropTypes | TypeScript | Instructions |
| -------------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PropTypes.any` | `T`, `Record<string, unknown>` or `any` | Figure out what would be the correct data type and use it.<br><br>If you know that it's a object but isn't possible to determine the internal structure, use `Record<string, unknown>`. |
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
| `PropTypes.array` or `PropTypes.arrayOf(T)` | `T[]` or `Array<T>` | Convert to `T[]` or `Array<T>`, where `T` is the data type of the array.<br><br>If `T` isn't a primitive type, create a separate `type` for the object structure of your prop and use it. |
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
| `PropTypes.bool` | `boolean` | Convert to `boolean`. |
| `PropTypes.func` | `(arg1: Type1, arg2, Type2...) => ReturnType` | Convert to the function signature. |
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
| `PropTypes.number` | `number` | Convert to `number`. |
| `PropTypes.object`, `PropTypes.shape(T)` or `PropTypes.exact(T)` | `T` | If `T` isn't a primitive type, create a separate `type` for the `T` object structure of your prop and use it.<br><br>If you want an object but isn't possible to determine the internal structure, use `Record<string, unknown>`. |
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
| `PropTypes.objectOf(T)` | `Record<string, T>` | Convert to a `Record<string, T>` where `T` is the data type of your dictionary.<br><br>If `T` isn't a primitive type, create a separate `type` for the object structure and use it. |
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
| `PropTypes.string` | `string` | Convert to `string`. |
| `PropTypes.node` | `React.ReactNode` | Convert to `React.ReactNode`. `ReactNode` includes `ReactElement` as well as other types such as `strings`, `numbers`, `arrays` of the same, `null`, and `undefined` In other words, anything that can be rendered in React is a `ReactNode`. |
| `PropTypes.element` | `React.ReactElement` | Convert to `React.ReactElement`. |
| `PropTypes.symbol` | `symbol` | Convert to `symbol`. |
| `PropTypes.elementType` | `React.ElementType` | Convert to `React.ElementType`. |
| `PropTypes.instanceOf(T)` | `T` | Convert to `T`. |
| `PropTypes.oneOf([T, U, ...])` or `PropTypes.oneOfType([T, U, ...])` | `T \| U \| ...` | Convert to a union type e.g. `T \| U \| ...`. |
197 changes: 197 additions & 0 deletions contributingGuides/TS_CHEATSHEET.md
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Expensify TypeScript React Native CheatSheet

## Table of Contents

- [1.1 `props.children`](#children-prop)
- [1.2 `forwardRef`](#forwardRef)
- [1.3 Animated styles](#animated-style)
- [1.4 Style Props](#style-props)
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
- [1.5 Render Prop](#render-prop)
- [1.6 Type Narrowing](#type-narrowing)
- [1.7 Errors in Type Catch Clauses](#try-catch-clauses)

## CheatSheet
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved

<a name="children-prop"></a><a name="1.1"></a>

- [1.1](#children-prop) **`props.children`**

```tsx
type WrapperComponentProps = {
children?: React.ReactNode;
};

function WrapperComponent({ children }: Props) {
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
return <View>{children}</View>;
}

function App() {
return (
<WrapperComponent>
<View />
</WrapperComponent>
);
}
```

<a name="forwardRef"></a><a name="1.2"></a>

- [1.2](#forwardRef) **`forwardRef`**

```ts
import { forwardRef, useRef, ReactNode } from "react";
import { TextInput, View } from "react-native";

export type CustomButtonProps = {
label: string;
children?: ReactNode;
};

const CustomTextInput = forwardRef<TextInput, CustomButtonProps>(
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
(props, ref) => {
return (
<View>
<TextInput ref={ref} />
{props.children}
</View>
);
}
);

function ParentComponent() {
const ref = useRef<TextInput>;
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
return <CustomTextInput ref={ref} label="Press me" />;
}
```

<a name="animated-style"></a><a name="1.3"></a>

- [1.3](#animated-style) **Animated styles**

```ts
import {useRef} from 'react';
import {Animated, StyleProp, ViewStyle} from 'react-native';

type MyComponentProps = {
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
};

function MyComponent({ style }: Props) {
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
return <Animated.View style={style} />;
}

function MyComponent() {
const anim = useRef(new Animated.Value(0)).current;
return <Component style={{opacity: anim.interpolate({...})}} />;
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
}
```

<a name="style-props"></a><a name="1.4"></a>

- [1.4](#style-props) **Style Props**

When converting or typing style props, use `StyleProp<T>` type where `T` is the type of styles related to the component your prop is going to apply.
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved

```tsx
import { StyleProp, ViewStyle, TextStyle, ImageStyle } from "react-native";

type MyComponentProps = {
containerStyle?: StyleProp<ViewStyle>;
textStyle?: StyleProp<TextStyle>;
imageStyle?: StyleProp<ImageStyle>;
};

function MyComponentProps({ containerStyle, textStyle, imageStyle }: MyComponentProps) = {
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
<View style={containerStyle}>
<Text style={textStyle}>Sample Image</Text>
<Image style={imageStyle} src={'https://sample.com/image.png'} />
</View>
}
```

<a name="render-prop"></a><a name="1.5"></a>

- [1.5](#render-prop) **Render Prop**

```tsx
type ParentComponentProps = {
children: (label: string) => React.ReactNode;
};

function ParentComponent({ children }: Props) {
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
return children("String being injected");
}

function App() {
return (
<ParentComponent>
{(label) => (
<View>
<Text>{label}</Text>
</View>
)}
</ParentComponent>
);
}
```

<a name="type-narrowing"></a><a name="1.6"></a>

- [1.6](#type-narrowing) **Type Narrowing** Narrow types down using `typeof` or custom type guards.

```ts
type Manager = {
role: "manager";
team: string;
};

type Engineer = {
role: "engineer";
language: "ts" | "js" | "php";
};

function introduce(employee: Manager | Engineer) {
console.log(employee.team); // TypeScript errors: Property 'team' does not exist on type 'Manager | Engineer'.

if (employee.role === "manager") {
console.log(`I manage ${employee.team}`); // employee: Manager
} else {
console.log(`I write ${employee.language}`); // employee: Engineer
}
}
```

In the above code, type narrowing is used to determine whether an employee object is a Manager or an Engineer based on the role property, allowing safe access to the `team` property for managers and the `language` property for engineers.

We can also create a custom type guard function.

```ts
function isManager(employee: Manager | Engineer): employee is Manager {
return employee.role === "manager";
}

function introduce(employee: Manager | Engineer) {
if (isManager(employee)) {
console.log(`I manage ${employee.team}`); // employee: Manager
}
}
```

In the above code, `employee is Manager` is Type Predicate. It signifies that the return type of `isManager` is a `boolean`, indicating whether a value passed to the function is of a certain type (e.g. `Manager`).
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved

<a name="try-catch-clauses"></a><a name="1.7"></a>

- [1.7](#try-catch-clauses) **Error in Try Catch Clauses**

Errors in try/catch clauses are typed as unknown, if the developer needs to use the error data they must conditionally check the type of the data first. Use type narrowing
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved

```ts
try {
....
} catch (e) { // `e` is `unknown`.
if (e instanceof Error) {
// you can access properties on Error
console.error(e.message);
}
}
```
Loading