Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
"start:build": "yarn build && yarn example build",
"start:debug": "yarn build && yarn example debug",
"test": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --experimental-vm-modules\" jest",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit && tsc --noEmit -p src/__tests__/types"
},
"keywords": [
"react-native",
Expand Down
103 changes: 103 additions & 0 deletions src/__tests__/types/_exact-optional-props.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type {
ButtonProps,
FlatListProps,
ImageBackgroundProps,
ImageProps,
InputAccessoryViewProps,
KeyboardAvoidingViewProps,
ModalProps,
ScrollViewProps,
StatusBarProps,
SwitchProps,
TextInputProps,
TextProps,
TouchableWithoutFeedbackProps,
ViewProps,
} from "react-native";

import type { VirtualizedListWithoutRenderItemProps } from "@react-native/virtualized-lists";

// Every prop this package adds must accept an explicit `undefined`, as React Native's own
// optional props already do. Under `exactOptionalPropertyTypes` a bare `?: string` rejects
// it, so `className={condition ? "p-4" : undefined}` — the ordinary conditional — fails.
// This file compiles with that flag on; the root typecheck cannot observe the difference.
declare const maybeString: string | undefined;
declare const maybeBoolean: boolean | undefined;
declare const noop: () => void;

export const view: ViewProps = {
className: maybeString,
cssInterop: maybeBoolean,
};

export const text: TextProps = {
className: maybeString,
cssInterop: maybeBoolean,
};

export const image: ImageProps = {
className: maybeString,
cssInterop: maybeBoolean,
};

export const switchProps: SwitchProps = {
className: maybeString,
cssInterop: maybeBoolean,
};

export const inputAccessoryView: InputAccessoryViewProps = {
className: maybeString,
cssInterop: maybeBoolean,
};

export const touchableWithoutFeedback: TouchableWithoutFeedbackProps = {
className: maybeString,
cssInterop: maybeBoolean,
};

export const statusBar: StatusBarProps = {
className: maybeString,
cssInterop: maybeBoolean,
};

export const button: ButtonProps = {
title: "",
onPress: noop,
className: maybeString,
};

export const scrollView: ScrollViewProps = {
contentContainerClassName: maybeString,
indicatorClassName: maybeString,
};

export const flatList: FlatListProps<unknown> = {
data: [],
renderItem: () => null,
columnWrapperClassName: maybeString,
};

export const imageBackground: ImageBackgroundProps = {
source: 0,
imageClassName: maybeString,
};

export const textInput: TextInputProps = {
placeholderClassName: maybeString,
};

export const keyboardAvoidingView: KeyboardAvoidingViewProps = {
contentContainerClassName: maybeString,
};

export const modal: ModalProps = {
presentationClassName: maybeString,
};

export const virtualizedList: VirtualizedListWithoutRenderItemProps<unknown> = {
data: [],
getItem: () => undefined,
getItemCount: () => 0,
ListFooterComponentClassName: maybeString,
ListHeaderComponentClassName: maybeString,
};
8 changes: 8 additions & 0 deletions src/__tests__/types/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"exactOptionalPropertyTypes": true
},
"include": ["./*"],
"files": ["../../../types.d.ts"]
}
6 changes: 3 additions & 3 deletions src/runtime.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type StyledReactElement<
: M[K] extends true | string | object
? K
: never
: never]?: string;
: never]?: string | undefined;
}
>;

Expand All @@ -41,7 +41,7 @@ export type StyledProps<P, M extends StyledConfiguration<any>> = P & {
: M[K] extends true | string | object
? K
: never
: never]?: string;
: never]?: string | undefined;
};

export type Styled = <
Expand All @@ -64,7 +64,7 @@ type StyledComponent<
: M[K] extends true | string | object
? K
: never
: never]?: string;
: never]?: string | undefined;
}
>;

Expand Down
52 changes: 28 additions & 24 deletions types.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// Every added prop is declared `| undefined` explicitly so the declarations
// hold under `exactOptionalPropertyTypes`, where `?: string` forbids passing an
// explicit `undefined` — which is what a conditional `className={x ? a : undefined}`
// spreads, and what every optional prop in React Native's own types already allows.
import type {
ScrollViewProps,
ScrollViewPropsAndroid,
Expand All @@ -10,64 +14,64 @@ import type {
declare module "@react-native/virtualized-lists" {
export interface VirtualizedListWithoutRenderItemProps<ItemT>
extends ScrollViewProps {
ListFooterComponentClassName?: string;
ListHeaderComponentClassName?: string;
ListFooterComponentClassName?: string | undefined;
ListHeaderComponentClassName?: string | undefined;
}
}

declare module "react-native" {
interface ButtonProps {
className?: string;
className?: string | undefined;
}
interface ScrollViewProps
extends ViewProps,
ScrollViewPropsIOS,
ScrollViewPropsAndroid,
Touchable {
contentContainerClassName?: string;
indicatorClassName?: string;
contentContainerClassName?: string | undefined;
indicatorClassName?: string | undefined;
}
interface FlatListProps<ItemT> extends VirtualizedListProps<ItemT> {
columnWrapperClassName?: string;
columnWrapperClassName?: string | undefined;
}
interface ImageBackgroundProps extends ImagePropsBase {
imageClassName?: string;
imageClassName?: string | undefined;
}
interface ImagePropsBase {
className?: string;
cssInterop?: boolean;
className?: string | undefined;
cssInterop?: boolean | undefined;
}
interface ViewProps {
className?: string;
cssInterop?: boolean;
className?: string | undefined;
cssInterop?: boolean | undefined;
}
interface TextInputProps {
placeholderClassName?: string;
placeholderClassName?: string | undefined;
}
interface TextProps {
className?: string;
cssInterop?: boolean;
className?: string | undefined;
cssInterop?: boolean | undefined;
}
interface SwitchProps {
className?: string;
cssInterop?: boolean;
className?: string | undefined;
cssInterop?: boolean | undefined;
}
interface InputAccessoryViewProps {
className?: string;
cssInterop?: boolean;
className?: string | undefined;
cssInterop?: boolean | undefined;
}
interface TouchableWithoutFeedbackProps {
className?: string;
cssInterop?: boolean;
className?: string | undefined;
cssInterop?: boolean | undefined;
}
interface StatusBarProps {
className?: string;
cssInterop?: boolean;
className?: string | undefined;
cssInterop?: boolean | undefined;
}
interface KeyboardAvoidingViewProps extends ViewProps {
contentContainerClassName?: string;
contentContainerClassName?: string | undefined;
}
interface ModalBaseProps {
presentationClassName?: string;
presentationClassName?: string | undefined;
}
}