0

I want to build a component that allows me to have different button sizes with react-native-elements. To achieve this I build a custom component which have a property size and with this I access dynamically to a specific size of the button with its respective styles inside theme object. Everything works as expected, but I have the following error in typescript: TS2532: Object is possibly 'undefined'. every time I try to access to the sizes object inside theme with the bracket notation.

Custom button component

import React, { useContext } from 'react';
import { Button, FullTheme, ThemeContext } from 'react-native-elements';

export type Props = Button['props'];
export type Theme = Partial<FullTheme>;

const styles = {
  button: (theme: Partial<FullTheme>, size: string) => ({
    padding: theme?.Button?.sizes[size]?.padding, // problem here
  }),
  title: (theme: Partial<FullTheme>, size: string) => ({
    fontSize: theme?.Button?.sizes[size]?.fontSize, // problem here
    lineHeight: theme?.Button?.sizes[size]?.lineHeight, // problem here
    fontFamily: theme?.Button?.font?.fontFamily,
  }),
};

function ButtonElement(props: Props): JSX.Element {
  const {
    size = 'medium',
    children,
    ...rest
  } = props;
  const { theme } = useContext(ThemeContext);

  return (
    <Button
      titleStyle={styles.title(theme, size)}
      buttonStyle={styles.button(theme, size)}
      {...rest}
    >
      {children}
    </Button>
  );
}

theme.ts

export const theme = {
  Button: {
    font: {
      fontFamily: 'inter-display-bold',
    },
    sizes: {
      small: {
        fontSize: 14,
        padding: 10,
        lineHeight: 20,
      },
      medium: {
        fontSize: 18,
        padding: 14,
        lineHeight: 24,
      },
      large: {
        fontSize: 20,
        padding: 18,
        lineHeight: 24,
      },
    },
  },
}

// react-native-elements.d.ts -> Extending the default theme to manage button sizes 
import 'react-native-elements';
import { StyleProp, TextStyle } from 'react-native';

export type Sizes = {[index: string]: TextStyle};
export type Size = 'small' | 'medium' | 'large';

declare module 'react-native-elements' {
  export interface ButtonProps {
    font?: TextStyle;
    sizes?: Sizes;
    size?: Size;
  }

  export interface FullTheme {
    Button: Partial<ButtonProps>;
  }
}

pass theme object to the components tree

// pass theme to the component tree
import { theme } from '@common/styles/theme';

export default function App(): JSX.Element | null {
  return (
    <ThemeProvider theme={theme}>
      <SafeAreaProvider>
        <Navigation />
        <StatusBar />
      </SafeAreaProvider>
    </ThemeProvider>
  );
}

what I have tried

  • I've used the ? operator as suggested by this answer.
  • I also used some suggestions mentioned in this post, using if statement to verify theme is not undefined.
Cristian Flórez
  • 649
  • 1
  • 9
  • 17

1 Answers1

0

Try doing theme?.Button?.sizes?[size]?.fontSize. You have annotated the theme parameter of the button and title functions to be of type Partial<FullTheme>. Partial<> is used to indicate that each member may possibly be undefined. Using the optional chaining operator short circuits the expression to be undefined if the preceding variable is undefined at runtime.

If you know that the whole path of member access in theme?.Button?.sizes[size]?.fontSize at runtime, then you can use an exclamation mark instead of a question mark. The exclamation mark is TypeScript's non-null assertion type-operator. It is a compile-time concept.

David Fong
  • 36
  • 4
  • but I do this in button and title functions, I use `?` operator. – Cristian Flórez Feb 02 '21 at 21:28
  • Oh my goodness I'm so sorry. I did a bad copy-paste and forgot to make the change I meant to suggest. I have edited my answer. I added a question mark after "sizes". Can you give it a try? – David Fong Feb 03 '21 at 03:13