Search code examples
typescriptmenuantd

How to make a dynamic menu using antd in typescript


I'm trying to build a dynamic menu component in antd using typescript.

I have checked this solution in javascript React Antd dynamic dropdown menu.

But when using typescript, it shows that item only have attribute className, key, style.

So how could i do it in ts?

Here's my code and it failes to build at icon={item?.icon} and {item?.label}.

import type { MenuProps } from 'antd';

function CustomAppBarMenu() {
    const [ menuItems, setMenuItems ] = useState<MenuProps['items']>([]);

    const main_window_menu_items: MenuProps['items'] = [
        {
            key: 'setting',
            label: 'Setting',
            icon: <IconSetting/>,
        },
        {
            key: 'upgrade',
            label: 'Upgrade',
            icon: <IconUpgrade />,
            disabled: true,
        },
        {
            key: 'help',
            label: 'Help',
            icon: <IconHelp />,
        },
        {
            key: 'about',
            label: 'About',
            icon: <IconAbout />,
        },
    ];

    const edit_menu_items: MenuProps['items'] = [
        {
            key: 'import',
            label: 'Import',
            icon: <IconImport/>,
        },
        {
            key: 'export',
            label: 'Export',
            icon: <IconExport />,
        },
    ];

    const handleMenuClick: MenuProps['onClick'] = ({ key }) => {
        console.log(`Click on item ${key}`);
    }

    useEffect(() => {
        if (appWindow.label === 'main') {
            setMenuItems(main_window_menu_items);
        } else if (appWindow.label === 'edit') {
            setMenuItems(edit_menu_items);
        }
    })

    return (
        <Menu onClick={handleMenuClick}>
            {
                menuItems?.map((item) => {
                    return (
                        <Menu.Item key={item?.key} icon={item?.icon}>
                            {item?.label}
                        </Menu.Item>
                    )
                })
            }
        </Menu>
    )
}

Solution

  • ItemType is a union type of MenuItemType | SubMenuType | MenuItemGroupType | MenuDividerType | null

    only 2 types have icon property and not all of them have label property

    # ./node_modules/antd/es/menu/hooks/useItems.d.ts
    export type ItemType = MenuItemType | SubMenuType | MenuItemGroupType | MenuDividerType | null;
    

    So, you either

    1. do type narrowing inside the map function (I don't know if it's possible)
    2. or use exact menu item types that have icon & label properties

    Code #2 (exact menu item types)

    import React, { useState, useEffect } from 'react';
    import { Menu } from 'antd';
    
    import type { ItemType, MenuItemType, SubMenuType } from "antd/es/menu/hooks/useItems";
    
    const IconSample = () => <span>tada!</span>;
    
    const appWindow = {
      label: 'main'
    };
    
    type MyItemType = MenuItemType | SubMenuType;
    
    const mainWindowMenuItems: MyItemType[] = [
      {
        key: 'setting',
        label: 'Setting',
        icon: <IconSample />,
      },
      {
        key: 'upgrade',
        label: 'Upgrade',
        icon: <IconSample />,
        disabled: true,
      },
      {
        key: 'help',
        label: 'Help',
        icon: <IconSample />,
      },
      {
        key: 'about',
        label: 'About',
        icon: <IconSample />,
      }
    ];
    
    const editMenuItems: MyItemType[] = [
      {
        key: 'import',
        label: 'Import',
        icon: <IconSample />,
      },
      {
        key: 'export',
        label: 'Export',
        icon: <IconSample />,
      },
    ];
    
    export function CustomAppBarMenu() {
      let menuItems: MyItemType[] = [];
    
      if (appWindow.label === 'main') {
        menuItems = mainWindowMenuItems;
      } else if (appWindow.label === 'edit') {
        menuItems = editMenuItems;
      }
    
      return (
        <Menu>
          {
            menuItems?.map((item) => {
              return (
                <Menu.Item key={item?.key} icon={item?.icon}>
                  {item?.label}
                </Menu.Item>
              )
            })
          }
        </Menu>
      )
    }