3

I'm struggling to extend the react-native-paper components correctly. What am I doing wrong or how to do it correctly? actually, I'm not totally new to Typescript, but I always struggled with this one:

// TextInput.tsx
import React from "react";
import { TextInput as PaperTextInput } from "react-native-paper";
import { TextInputProps } from "react-native-paper/lib/typescript/src/components/TextInput/TextInput";

interface MyTextInputProps extends TextInputProps {}

const MyTextInput: React.FC<MyTextInputProps> = (props) => {
  return <PaperTextInput {...props} />;
};

export default MyTextInput;
// Usage:
<TextInput mode="outlined" style={styles.input} label="Email" />

And I get this error:

Property 'theme' is missing in type '{ mode: "outlined"; style: { alignSelf: "stretch"; }; label: string; }' but required in type 'MyTextInputProps'

3 Answers 3

5

It looks like TextInputProps isn't actually the type of the props of TextInput. 😕 This is probably because the exported component is wrapped with withTheme():

export default withTheme(TextInput);

It gets real messy in the final compiled types:

declare const _default: (React.ComponentClass<Pick<TextInputProps, "ref" | "label" | "style" | "children" | "pointerEvents" | "allowFontScaling" | "numberOfLines" | "onLayout" | "onPressIn" | "onPressOut" | "testID" | "nativeID" | "maxFontSizeMultiplier" | "selectionColor" | "textBreakStrategy" | "accessible" | "accessibilityActions" | "accessibilityLabel" | "accessibilityRole" | "accessibilityState" | "accessibilityHint" | "accessibilityValue" | "onAccessibilityAction" | "accessibilityLiveRegion" | "importantForAccessibility" | "accessibilityElementsHidden" | "accessibilityViewIsModal" | "onAccessibilityEscape" | "onAccessibilityTap" | "onMagicTap" | "accessibilityIgnoresInvertColors" | "key" | "hitSlop" | "removeClippedSubviews" | "collapsable" | "needsOffscreenAlphaCompositing" | "renderToHardwareTextureAndroid" | "focusable" | "shouldRasterizeIOS" | "isTVSelectable" | "hasTVPreferredFocus" | "tvParallaxProperties" | "tvParallaxShiftDistanceX" | "tvParallaxShiftDistanceY" | "tvParallaxTiltAngle" | "tvParallaxMagnification" | "onStartShouldSetResponder" | "onMoveShouldSetResponder" | "onResponderEnd" | "onResponderGrant" | "onResponderReject" | "onResponderMove" | "onResponderRelease" | "onResponderStart" | "onResponderTerminationRequest" | "onResponderTerminate" | "onStartShouldSetResponderCapture" | "onMoveShouldSetResponderCapture" | "onTouchStart" | "onTouchMove" | "onTouchEnd" | "onTouchCancel" | "onTouchEndCapture" | "render" | "left" | "right" | "disabled" | "onBlur" | "onFocus" | "multiline" | "value" | "mode" | "error" | "placeholder" | "textAlign" | "textAlignVertical" | "onContentSizeChange" | "onScroll" | "scrollEnabled" | "autoCapitalize" | "autoCorrect" | "autoFocus" | "blurOnSubmit" | "caretHidden" | "contextMenuHidden" | "defaultValue" | "editable" | "keyboardType" | "maxLength" | "onChange" | "onChangeText" | "onEndEditing" | "onSelectionChange" | "onSubmitEditing" | "onTextInput" | "onKeyPress" | "placeholderTextColor" | "returnKeyType" | "secureTextEntry" | "selectTextOnFocus" | "selection" | "inputAccessoryViewID" | "clearButtonMode" | "clearTextOnFocus" | "dataDetectorTypes" | "enablesReturnKeyAutomatically" | "keyboardAppearance" | "passwordRules" | "rejectResponderTermination" | "selectionState" | "spellCheck" | "textContentType" | "autoComplete" | "importantForAutofill" | "disableFullscreenUI" | "inlineImageLeft" | "inlineImagePadding" | "returnKeyLabel" | "underlineColorAndroid" | "showSoftInputOnFocus" | "underlineColor" | "activeUnderlineColor" | "outlineColor" | "activeOutlineColor" | "dense"> & {
    theme?: import("@callstack/react-theme-provider").$DeepPartial<ReactNativePaper.Theme> | undefined;
}, any> & import("@callstack/react-theme-provider/typings/hoist-non-react-statics").NonReactStatics<(React.ComponentClass<TextInputProps, any> & typeof TextInput) | (React.FunctionComponent<TextInputProps> & typeof TextInput), {}>) | (React.FunctionComponent<Pick<TextInputProps, "ref" | "label" | "style" | "children" | "pointerEvents" | "allowFontScaling" | "numberOfLines" | "onLayout" | "onPressIn" | "onPressOut" | "testID" | "nativeID" | "maxFontSizeMultiplier" | "selectionColor" | "textBreakStrategy" | "accessible" | "accessibilityActions" | "accessibilityLabel" | "accessibilityRole" | "accessibilityState" | "accessibilityHint" | "accessibilityValue" | "onAccessibilityAction" | "accessibilityLiveRegion" | "importantForAccessibility" | "accessibilityElementsHidden" | "accessibilityViewIsModal" | "onAccessibilityEscape" | "onAccessibilityTap" | "onMagicTap" | "accessibilityIgnoresInvertColors" | "key" | "hitSlop" | "removeClippedSubviews" | "collapsable" | "needsOffscreenAlphaCompositing" | "renderToHardwareTextureAndroid" | "focusable" | "shouldRasterizeIOS" | "isTVSelectable" | "hasTVPreferredFocus" | "tvParallaxProperties" | "tvParallaxShiftDistanceX" | "tvParallaxShiftDistanceY" | "tvParallaxTiltAngle" | "tvParallaxMagnification" | "onStartShouldSetResponder" | "onMoveShouldSetResponder" | "onResponderEnd" | "onResponderGrant" | "onResponderReject" | "onResponderMove" | "onResponderRelease" | "onResponderStart" | "onResponderTerminationRequest" | "onResponderTerminate" | "onStartShouldSetResponderCapture" | "onMoveShouldSetResponderCapture" | "onTouchStart" | "onTouchMove" | "onTouchEnd" | "onTouchCancel" | "onTouchEndCapture" | "render" | "left" | "right" | "disabled" | "onBlur" | "onFocus" | "multiline" | "value" | "mode" | "error" | "placeholder" | "textAlign" | "textAlignVertical" | "onContentSizeChange" | "onScroll" | "scrollEnabled" | "autoCapitalize" | "autoCorrect" | "autoFocus" | "blurOnSubmit" | "caretHidden" | "contextMenuHidden" | "defaultValue" | "editable" | "keyboardType" | "maxLength" | "onChange" | "onChangeText" | "onEndEditing" | "onSelectionChange" | "onSubmitEditing" | "onTextInput" | "onKeyPress" | "placeholderTextColor" | "returnKeyType" | "secureTextEntry" | "selectTextOnFocus" | "selection" | "inputAccessoryViewID" | "clearButtonMode" | "clearTextOnFocus" | "dataDetectorTypes" | "enablesReturnKeyAutomatically" | "keyboardAppearance" | "passwordRules" | "rejectResponderTermination" | "selectionState" | "spellCheck" | "textContentType" | "autoComplete" | "importantForAutofill" | "disableFullscreenUI" | "inlineImageLeft" | "inlineImagePadding" | "returnKeyLabel" | "underlineColorAndroid" | "showSoftInputOnFocus" | "underlineColor" | "activeUnderlineColor" | "outlineColor" | "activeOutlineColor" | "dense"> & {
    theme?: import("@callstack/react-theme-provider").$DeepPartial<ReactNativePaper.Theme> | undefined;
}> & import("@callstack/react-theme-provider/typings/hoist-non-react-statics").NonReactStatics<(React.ComponentClass<TextInputProps, any> & typeof TextInput) | (React.FunctionComponent<TextInputProps> & typeof TextInput), {}>);
export default _default;

Instead of TextInputProps, you can use React.ComponentProps<typeof TextInput>. But because that's not an interface, you can't extend it into a new interface; you'll have to use an object type instead.

// TextInput.tsx
import React from "react";
import { TextInput as PaperTextInput } from "react-native-paper";

type MyTextInputProps = React.ComponentProps<typeof PaperTextInput> & {
  // ...
}

const MyTextInput: React.FC<MyTextInputProps> = (props) => {
  return <PaperTextInput {...props} />;
};

export default MyTextInput;
Sign up to request clarification or add additional context in comments.

1 Comment

nice, I get the feeling that React.ComponentProps<typeof TextInput> is an extremely useful trick for situations like these. It looks safer than other options because it's guaranteed to be the props of the component and should work with any component
0

If you look at the Typescript declaration of the TextInputProps class/interface, it'll have a theme property that is not optional. However, your import there looks really fishy - maybe just use an interface of your own for the props that exposes just what you actually need?

A quick solution to get rid of the warning would be to use Partial:

interface MyTextInputProps extends Partial<TextInputProps> {}

Note that this will stop TS complaints about any missing properties, but then, all others seem to be optional anyway (no idea why the theme one isn't).

2 Comments

That's what I will do If I will not find any other good solution. I was just wondering if it's possible to use original interface, so when I want to new prop (not used before), I don't have to update this component
You could still use Partial<T> in that case: interface MyTextInputProps extends Partial<TextInputProps> That would give you IDE support, but no warnings if you omit any properties. Of course, it also wouldn't force you to provide some that are missing unless they are declared in your custom interface.
0

You can use defaultProps to set a theme and it should solve the problem.

import React from "react";
import { TextInput as PaperTextInput, DefaultTheme } from "react-native-paper";

interface Props extends TextInputProps {
  yourCustomProperty: string;
}

const MyTextInput = (props : Props) => {
  return <PaperTextInput {...props} />;
};

MyTextInput.defaultProps = {
  theme: DefaultTheme
}

export default MyTextInput;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.