skip to Main Content

I’m trying to create an abstraction for a component by passing a component as a prop. In short, I want to be able to pass a TriggerComponent that can be either a View, Button, or Whatever other component as long as it can take an onPress prop.

I have a shared Menu component here:

// src/components/shared/Menu.tsx

import { ReactNode, FunctionComponent, useRef } from 'react';
import {
  Menu as PMMenu,
  MenuOptions,
  MenuTrigger,
  withMenuContext,
} from 'react-native-popup-menu';
import { View } from 'react-native';

interface IMenu {
  children: ReactNode,
  ctx: {
    menuActions: {
      toggleMenu: (name: string) => void,
    },
  },
  TriggerComponent: FunctionComponent,
  triggerProps: Object,
}

function Menu({
  ctx: { menuActions: { toggleMenu } },
  children,
  TriggerComponent,
  triggerProps,
}: IMenu)
  ...
  return (
    <View>
      <TriggerComponent
        {...triggerProps}
        onPress={() => toggleMenu(menuRef.current?.props?.menuName)} // <-- ERROR: Property 'onPress' does not exist on type 'IntrinsicAttributes'.ts(2322)
      />
      <PMMenu>
        <MenuTrigger ref={menuRef} />
        <MenuOptions customStyles={optionsStyles}>
          {children}
        </MenuOptions>
      </PMMenu>
    </View>
  );

The code calling the Menu component:

function UserHeaderButton() {
  const nav = useNavigation<any>();
  return (
    <Menu
      TriggerComponent={Item}
      triggerProps={{
        title: 'Account',
        iconName: 'account-circle',
      }}
    >
      <MenuOption onSelect={() => nav.navigate('auth', { screen: 'login' })} text="Login" />
      <MenuOption onSelect={() => nav.navigate('auth', { screen: 'signup' })} text="Sign Up" />
    </Menu>
  );
}

If I define triggerProps as any, the error disappears, which is weird because the onPress prop isn’t included in that object.

2

Answers


  1. Chosen as BEST ANSWER

    Here's what I ended up doing.

    In my Menu component:

    // src/components/shared/Menu.tsx
    
    import { ReactNode, FunctionComponent, useRef } from 'react';
    import {
      Menu as PMMenu,
      MenuOptions,
      MenuTrigger,
      withMenuContext,
    } from 'react-native-popup-menu';
    import { View } from 'react-native';
    
    interface ITriggerProps {
      onPress: Function,
    }
    
    interface IMenu {
      children: ReactNode,
      ctx: {
        menuActions: {
          toggleMenu: (name: string) => void,
        },
      },
      TriggerComponent: FC<ITriggerProps>,
      triggerProps: Object,
    }
    
    function Menu({
      ctx: { menuActions: { toggleMenu } },
      children,
      TriggerComponent,
      triggerProps,
    }: IMenu)
      ...
      return (
        <View>
          <TriggerComponent
            {...triggerProps}
            onPress={() => toggleMenu(menuRef.current?.props?.menuName)}
          />
          <PMMenu>
            <MenuTrigger ref={menuRef} />
            <MenuOptions customStyles={optionsStyles}>
              {children}
            </MenuOptions>
          </PMMenu>
        </View>
      );
    

  2. You can do the the following.

    interface ITriggerProps {
      title: string
      iconName: string
    }
    
    interface IMenu {
      children: ReactNode,
      ctx: {
        menuActions: {
          toggleMenu: (name: string) => void,
        },
      },
      TriggerComponent: React.FC<ITriggerProps>
    }
    

    Then you can pass a component like the following.

       <Menu
          TriggerComponent={<Item title="Title" iconName="some-icon" />}
       >
    

    This is also completely type-safe. Hope this helps.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search