Ant Design: Correct type of Menu onClick event parameter - reactjs

I am trying to use a navigation component from Antd (https://ant.design/components/menu), but it's giving me this error:
Parameter 'e' implicitly has an 'any' type.
I have tried doing e: React.MouseEvent<HTMLElement> but then the error shifts to e.key: Property 'key' does not exist on type 'MouseEvent<HTMLInputElement, MouseEvent>'.
Following is my App.tsx:
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '#ant-design/icons';
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
const items: MenuProps['items'] = [
{
label: 'Navigation One',
key: 'mail',
icon: <MailOutlined />,
},
{
label: 'Navigation Two',
key: 'app',
icon: <AppstoreOutlined />,
disabled: true,
},
{
label: 'Navigation Three - Submenu',
key: 'SubMenu',
icon: <SettingOutlined />,
children: [
{
type: 'group',
label: 'Item 1',
children: [
{
label: 'Option 1',
key: 'setting:1',
},
{
label: 'Option 2',
key: 'setting:2',
},
],
},
{
type: 'group',
label: 'Item 2',
children: [
{
label: 'Option 3',
key: 'setting:3',
},
{
label: 'Option 4',
key: 'setting:4',
},
],
},
],
},
{
label: (
<a href="https://ant.design" target="_blank" rel="noopener noreferrer">
Navigation Four - Link
</a>
),
key: 'alipay',
},
];
const App: React.FC = () => {
const [current, setCurrent] = useState('mail');
const onClick: MenuProps['onClick'] = (e) => {
console.log('click ', e);
setCurrent(e.key);
};
return <Menu onClick={(onClick)} selectedKeys={[current]} mode="horizontal" items={items} />;
};
export default App;
I am new to this and have already spent many hours trying to fix this, any help is highly appreciated! Thank you!

The correct type of e is the MenuInfo interface defined in the react-component library (rc-menu). Unfortunately, this type isn't exported. As a workaround, you can use the following code to indirectly reference the MenuInfo interface:
const onClick: MenuProps['onClick'] = (e: Parameters<MenuProps['onClick']>[0]) => {
The Parameters utility type returns an array type containing the parameter types of the specified function, and the [0] indexer returns the type of the first (and only) parameter.

Related

How to add a changeable icon to component in storybook?

I have a component - a simple list with an icon, header and links. I need to add an icon in front of the header. So that you can choose which icon will be in the Storybook. I can't find it in the documentation. And I can't figure out how to do it. If you know how, please help me.
// ItemsList.tsx
import React, { ReactElement, ReactNode } from 'react';
import { myIcon1 } from 'src/icons/Icon1';
import { myIcon2 } from 'src/icons/Icon2';
interface ItemsList {
icon: ReactElement;
header: string;
children?: ReactNode;
}
export const ItemsList = ({ icon, header, children }: ItemsList) => (
<div>
{icon} // Have no idea how to add one of imported icons here??
<div>{header}</div>
<ul>{children}</ul>
</div>
);
My Story File:
// ItemsList.story.jsx
import { ItemsList } from './itemsList';
export default {
title: 'ItemsList',
component: ItemsList,
argTypes: {
icon: {
control: { type: 'radio', options: ['no icon', 'icon 1', 'icon 2'] },
},
header: {
type: 'string',
defaultValue: 'Header',
},
children: {
type: 'array',
name: 'label',
defaultValue: ['link 1', 'link 2', 'link 3'],
},
},
};
const Template = (args) => <ItemsList {...args} />;
export const Default = Template.bind({});
Default.args = {
icon: ????,
header: 'Header',
children: ['link 1', 'link 2', 'link 3'],
};

how can we have different functions on ANt design menu items in the new version?

I'm sure if you're using ant design menu component you've got the same error:
Warning: [antd: Menu] children will be removed in next major version. Please use items instead.
the new way to make this is:
const items = [
{ label: 'item 1', key: 'item-1' },
{ label: 'item 2', key: 'item-2' },
{
label: 'sub menu',
key: 'submenu',
children: [{ label: 'item 3', key: 'submenu-item-1' }],
},
];
return <Menu items={items} />
and you can define your function and use it like this:
return <Menu onClick={onClick} selectedKeys={[current]} mode="horizontal" items={items} />;
how can i have different functions on my items?
i'll be glad if someone help me with this
in the old way i could easily define any functions i want on any items
Actually, we can add functions to the items array as well
const [current, setCurrent] = useState();
const func = () => {
setCurrent(e.key);
}
const func2 = () => {
setCurrent(e.key);
}
const items = [
{
label: 'item 1',
key: 'item-1'
onClick: func,
className: class1,
},
{
label: 'item 1',
key: 'item-1'
onClick: func2,
className: class2,
},,
{
label: 'sub menu',
key: 'submenu',
children: [{ label: 'item 3', key: 'submenu-item-1' }],
},
];
return <Menu selectedKeys={[current]} mode="horizontal" items={items} />;

Antd Cascader component value/defaultValue showing ID instead of label

So I am using the Antd Cascader component (for the first time, having used Antd Select for most other things for the past few months). However, it isn't quite working yet. I have the options basically like this (but with a lot more options, only 3 levels deep tho like this):
const options = [
{
label: 'Base 1',
value: 'base_1',
children: [
{
label: 'Middle a',
value: 'middle_a',
children: [
{
label: 'Foo',
value: 'd57b2b75-4afa-4a16-8991-fc736bce8cd5',
},
{
label: 'Bar',
value: 'd12b2b75-4afa-4a16-8991-fc736bce8cec',
}
]
},
{
label: 'Middle b',
value: 'middle_b',
children: [
{
label: 'Baz',
value: 'd32b2b75-4afa-4a16-8991-fc736bce8cdd',
},
{
label: 'Quux',
value: 'dabb2b75-4afa-4a16-8991-fc736bce8ced',
}
]
}
]
},
{
label: 'Base 2',
value: 'base_2',
children: [
{
label: 'Middle a',
value: 'middle_a',
children: [
{
label: 'Helo',
value: 'd32bce75-4afa-4a16-8991-fc736bce8cdd',
},
{
label: 'World',
value: 'dabbac75-4afa-4a16-8991-fc736bce8ced',
}
]
}
]
}
]
I configure the component like this:
<Cascader
options={options}
getPopupContainer={trigger => trigger.parentNode}
multiple={multiple}
displayRender={label => {
return label.join(' / ');
}}
value={defaultValue}
// eslint-disable-next-line #typescript-eslint/no-explicit-any
onChange={(val: any, options: any) => {
if (multiple) {
const ids = serializeCascaderOptionValues(options);
onChange?.(ids, options);
} else {
onChange?.(val[2] ? String(val[2]) : undefined, options);
}
}}
showSearch={{
filter: (input: string, path) => {
return path.some(
option =>
String(option?.label).toLowerCase().indexOf(input.toLowerCase()) >
-1
);
},
}}
/>
Where, here, value or defaultValue I am passing in an array like ['d32bce75-4afa-4a16-8991-fc736bce8cdd'], which maps to Base 2 / Middle a / Helo. When I select the value Base 2 / Middle a / Helo from the UI, it shows correctly in the input like that. But when I save it and refresh the page (persisting to the backend, and pass in value={value} like value={['d32bce75-4afa-4a16-8991-fc736bce8cdd']}, it shows the ID hash in the input instead of the nice string Base 2 / Middle a / Helo. When I log displayRender={label...}, the label is showing the [ID-hash], rather than an array of like ['Base 2', 'Middle a', 'Helo']. What am I doing wrong, how do I get it to show properly?

Pick a type from one of the props

I've a List component
type Option = {
id: string;
};
type Props<O> = {
options?: O[];
option?: JSXElementConstructor<O>;
};
const List = <O extends Option>(props: Props<O>) => ...
and some option components like this one
type Props = {
label: string;
icon?: ElementType;
onClick?: () => void;
};
const BasicOption = (props: Props) => ...
in order to get the right type I have to do this
const Example = () => {
const options = [
{ id: 'a', label: 'Label A', icon: PlaceholderIcon },
{ id: 'b', label: 'Label B', icon: PlaceholderIcon },
{ id: 'c', label: 'Label C', icon: PlaceholderIcon },
{ id: 'd', label: 'Label D', disabled: true },
{ id: 'e', label: 'Label E' },
];
return (
<List<typeof options[number]>
options={options}
option={BasicOption}
/>
);
};
is there a way to get the right typing directly from the array in order to avoid the <typeof options[number]> part?
Working example: https://codesandbox.io/s/vigorous-hopper-i41uj?file=/src/App.tsx
You need infer options prop and bind it with option.
import React, { JSXElementConstructor, ElementType } from "react";
export type BasicProps = {
label: string;
icon?: ElementType;
onClick?: () => void;
};
export const BasicOption = ({ label, icon: Icon, onClick }: BasicProps) => (
<div onClick={() => onClick?.()}>
{label}
{Icon && <Icon />}
</div>
);
export type Option = {
id: string;
};
export const List = <Opt extends Option, Options extends Opt[]>({
options,
option: Option
}: {
options: [...Options],
// same as typeof options[number] but Options is infered
option: JSXElementConstructor<[...Options][number]>;
}) => (
<div>
{Option &&
options &&
options.map((option) => <Option key={option.id} {...option} />)}
</div>
);
export default function App() {
const options = [
{ id: "a", label: "Label A" },
{ id: "b", label: "Label B" },
{ id: "c", label: "Label C" },
{ id: "d", label: "Label D", disabled: true },
{ id: "e", label: "Label E" }
];
return (
<List options={options} option={BasicOption} />
);
}
Playground
You can find more about function arguments inference in my blog

Using props in functional component

I am getting array in props which is props.logsInfo.logs and I want to use it according to the number of elements in array instead of hard coding it like props.logsInfo.logs[0]['id']. I want to use map.Logs array size can be more than one.I don't want to write one more cells: [
{ key: 'cell-0', children: props.logsInfo.logs[1]['id'] },How can i achieve it??
import React from 'react';
import Table from 'terra-table';
const StripedTable = props => (
<Table
summaryId="striped-table"
summary="This table displays striped rows."
numberOfColumns={4}
dividerStyle="horizontal"
headerData={{
cells: [
{ id: 'header-Id', key: 'id', children: 'Id' },
{ id: 'header-vendorId', key: 'vendorId', children: 'VendorId' },
{ id: 'header-userId', key: 'userId', children: 'UserId' },
{ id: 'header-payLoad', key: 'payLoad', children: 'PayLoad' },
],
}}
bodyData={[
{
rows: [
{
key: 'row-0',
cells: [
{ key: 'cell-0', children: props.logsInfo.logs[0]['id'] },
{ key: 'cell-1', children: props.logsInfo.logs[0]['vendorId'] },
{ key: 'cell-2', children: props.logsInfo.logs[0]['userId'] },
{ key: 'cell-3', children: props.logsInfo.logs[0]['payLoad']},
],
},
],
},
]}
/>
);
export default StripedTable;
Yes, you can use .map:
bodyData={[
{
rows: props.logsInfo.logs.map(log => ({
key: log.id,
cells: [
{ key: 'cell-0', children: log.id },
{ key: 'cell-1', children: log.vendorId },
{ key: 'cell-2', children: log.userId },
{ key: 'cell-3', children: log.payLoad},
],
}),
},
]}
You can also do the same for cells if you keep an array with the field names around:
const fields = ['id', 'vendorId', 'userId', 'payLoad'];
// ...
bodyData={[
{
rows: props.logsInfo.logs.map(log => ({
key: log.id,
cells: fields.map(field => ({key: field, children: log[field]})),
}),
},
]}

Resources