ant-design / ant-design

An enterprise-class UI design language and React UI library
https://ant.design
MIT License
90.7k stars 46.98k forks source link

A List of `antd`'s components that cannot work with HOC #4853

Closed benjycui closed 6 months ago

benjycui commented 7 years ago

It is not a common use case, so it's low priority. But we still can discuss and try to make it better:

benjycui commented 6 years ago

It's difficult, for some components use key as part of API, we cannot fix this until we rename all the API's names. e.g.

key => id
expandedKeys => expandedIds
selectedKeys => selectedIds
....

It will be a breaking change, but such kind of breaking change can be resolve by antd-codemod.

benjycui commented 6 years ago

SO, do you think that it is worthy doing so?

afc163 commented 6 years ago

I don't think this is a good idea.

MacKentoch commented 6 years ago

Not fan of codemod solution. Just my point of view, but, I believe that sadly for most of people it is more repulsive than welcomed.

Embedding components into custom components is a common practice in React.

Please fix it and see ant-design adoption fast increase with no doubt.

Ant Design is really appealing and is the first library that makes me want to leave react-bootstrap.

benjycui commented 6 years ago

@MacKentoch

It's difficult, for some components use key as part of API, we cannot fix this until we rename all the API's names.

benjycui commented 6 years ago

@afc163 if we cannot rename those APIs, then we can not resolve this problem. But we can provide a work around, see: https://github.com/react-component/collapse/issues/73#issuecomment-323626120

Do you think that we should add this to documentation?

MacKentoch commented 6 years ago

@benjycui I understand.

Anyway, it is not blocking after all.

Thank you for taking time answering.

ramiel commented 6 years ago

@benjycui I was investigating the workaround you proposed but I think it is not a proper solution. Usually when you wrap a component you want also to have some internal state. With the solution proposed this is not possible. Also I think this is not a little problem. Not being able to isolate common components means having the same code repeated lot of times inside an application. If the application is big I would consider not to adopt antd at all. Please consider this as a constructive critic. Thanks for your work!

bodaso commented 6 years ago

Agreed to say this is not a small problem and it's something I did not expect of when I started using Ant Design library, the good practice of custom components are used throughout React projects. Personally, I really like Ant Design, but for some, this could be a dealbreaker. Would really love to see this gets improved in the upcoming Ant Design v3.

mitjade commented 6 years ago

Please find a solution for this in v3.

yesmeck commented 6 years ago

Can be resolved after this package released (maybe).

yesmeck commented 6 years ago

https://github.com/ant-design/ant-design/issues/5540

ryanbas21 commented 6 years ago

just ran into this trying (sorry if this is wrong) to create a navbar using menu and nesting React Router <Link /> tags in the Menu with <Icon />.

Is there a more preferred solution?

abenhamdine commented 6 years ago

IMHO, this thread should be in the official docs, because this issue is likely to be a dealbreaker for many users. The docs should also mention https://github.com/react-component/collapse/issues/73#issuecomment-323626120 as an alternative when no state is needed.

ChuckJonas commented 6 years ago

I would have definitely appreciated mention of this in the documentation! I was trying to do something like this and wasted quite a bit of time because it doesn't work.

<Collapse>
   <MyCollapseItem />
   <MyCollapseItem2 />
</Collapse>

Where MyCollapseItem & MyCollapseItem2 render the Collapse.Panel.

Also, now that react16 allows you to return arrays of components from render, being able to do this would be especially helpful. Otherwise, prevent duplicate code is challenging.

lednhatkhanh commented 6 years ago

Any updates on this?

salim7 commented 6 years ago

This is actually a major issue for us. I'd vote for the renaming breaking change, as there is no workaround I can think of.

olivermack commented 6 years ago

Same here - I need that for Tabs.TabPane and Menu.MenuItem.

jfreixa commented 6 years ago

I found a workaround basically you can pass the rest of the props with <Tabs.TabPane {...props} /> from the wrapped component.

marceloemanoel commented 6 years ago

If you use with React.Children.map(children, (child, index) => { ... }) the workaround doesn't work, at least for me. In the end, the key property gets a value like mykey/.0 which is not what I can work. I'd also vote for a name change on the properties.

ChuckJonas commented 6 years ago

this issue may actually drive me away from antd... Or at least cause me to find a replacement for these components. It really limits your ability to create reusable components.

jkyletreman commented 6 years ago

For anyone trying to utilize React Router within a Menu.Item @yunshuipiao had a successful solution that worked for me in #6576

ncknuna commented 6 years ago

Another small issue: wrapping Menu.Item also prevents the Submenu from appearing selected when one of its child items is selected, at least in vertical mode. Relevant lines:

https://github.com/react-component/menu/blob/018d6f84d622ee140d9695ba57e7a773cf368efa/src/util.js#L40 https://github.com/react-component/menu/blob/018d6f84d622ee140d9695ba57e7a773cf368efa/src/SubMenu.jsx#L314

JustinOng commented 6 years ago

Thought I'ld document this for those who are trying to make Collapse work with a custom component: Similarly to what @jfreixa mentioned, just pass all the props given to the custom component to the Panel, eg

<Collapse>
  <Custom/>
  <Custom/>
</Collapse>

Custom.render() {
  return (
    <Panel {...this.props}>
      <div>My custom stuff here</div>
    </Panel>
  )
}
Nomeyho commented 6 years ago

Same issue than @ncknuna.

Menu.Item are not selected when wrapped. Is there a workaround?

ncknuna commented 6 years ago

@Nomeyho I ended up reconstructing the logic that determines whether the ant-menu-submenu-selected class gets added by copy/pasting the relevant methods and commenting out the original check, then passing the class as className in my wrapper:

function loopMenuItemRecusively (children: Array<any>, keys: Array<string>, ret: { find: boolean }) {
  if (!children || ret.find) {
    return
  }
  React.Children.forEach(children, (c: any) => {
    if (ret.find) {
      return
    }
    if (c) {
      const construt = c.type
      // Original check that caused problems. I'm not concerned about omitting it
      // because I don't plan on putting a bunch of weird, large stuff in my menus...
      // if (!construt || !(construt.isSubMenu || construt.isMenuItem || construt.isMenuItemGroup)) {
      //   return;
      // }
      if (keys.indexOf(c.key) !== -1) {
        ret.find = true
      } else if (c.props && c.props.children) {
        loopMenuItemRecusively(c.props.children, keys, ret)
      }
    }
  })
}

function isChildrenSelected (children: Array<any>, selectedKeys: Array<string>) {
  const ret = { find: false }
  loopMenuItemRecusively(children, selectedKeys, ret)
  return ret.find
}

// Omitting other custom code below...
export const SubMenu = ({ children, className, selectedKeys, title, ...rest }: any) => {
  const isSelected = isChildrenSelected(children, selectedKeys)
  className = [
    className,
    isSelected ? 'ant-menu-submenu-selected' : ''
  ].filter(className => classname).join(' ')
  return (
    <SubMenu title={title} className={className} selectedKeys={selectedKeys} {...rest}>
      {children}
    </SubMenu>
  )
}
RafaelRojasCov commented 6 years ago

Is there any fix for this?

I'm agree with @ChuckJonas

this issue may actually drive me away from antd... Or at least cause me to find a replacement for these components. It really limits your ability to create reusable components.

I need to use Menu SubMenu and Menu.items like this: Why? because i can use my "CustomSubMenu" elements in other pages... this is an important part of "reusable" components.

MainFile.js

Import CustomSubMenu from './OtherFile.js';

<Menu>
    <CustomSubMenu />
    <CustomSubMenu2 />
    <CustomSubMenu3 />
</Menu>

and OtherFile.js like this:

render(){
 return(
     <SubMenu>
           <SubMenu.item />
           <SubMenu.item2 />
            etc...etc...
     </SubMenu>
 );
}
leo-faimdata commented 6 years ago

Temporary workaround (edited for simplicity) for Submenus:

const SubMenuArray = ({ ...props }) =>
  [1, 2].map(i => (
    <Menu.SubMenu {...props} eventKey={`item_${i}`} subMenuKey={`${i}-menu-`} />
  ));

When dealing with arrays:

A better approach would probably be:

const SubMenuWrapper = ({ children, ...props }) =>
  React.Children.map(children, (child, i) =>
    React.cloneElement(child, {
      ...props,
      eventKey: `item_${i}`,
      subMenuKey: `${i}-menu-`
    })
  );

Usage:

      <Menu>
        <SubMenuWrapper>
          <CustomSubMenu title={"one"}/>
          <CustomSubMenu title={"two"}/>
        </SubMenuWrapper>
      </Menu>

Again, you probably do not want to use the index in the array, so don't use this in production, but the idea is solid.

ShawnFarsai commented 5 years ago
yesmeck commented 5 years ago

I think we have a way to remove the dependency of the key. Take Menu as an example, we can introduce an itemKey prop and then use the context to implement the Menu. In order to maintain compatibility, the Menu still traverses the children and change the key to itemKey if it presents. At the same time, we can also keep the semantics of props such as selectedKeys.

MacKentoch commented 5 years ago

@yesmeck to be honest it makes a while since I don't use ant design (but plan to use it for an important application within this week).

As far as I understand, you could benefit the react new context API as workaround?

That is great news

yesmeck commented 5 years ago

Yes, we need to context instead of cloneElement to solve the problem.

imdoge commented 5 years ago

I think a solution is not to use "React.Children.forEach" and "React.cloneElement" to pass props and set new props, use a function, for example to custom:

<Select>
  {({ onSelect }) => (
    <div>
      <Option onClick={onSelect} key="0">option1</Option>
      <Option onClick={onSelect} key="1">option2</Option>
    </div>
  )
</Select>

and antd select source also use function children props rather than "React.Children.forEach" and "React.cloneElement"

loxs commented 5 years ago

Excuse me if this is a stupid question, but I am still fairly new to React and Ant Design. Does this mean that practically we can't use Ant design menus inside a react-redux connected SPA? If so, how can you write a relatively complex SPA with Ant Design? Is there a workaround?

vylan commented 5 years ago

Any update on this?

krishna-aasaanjobs commented 5 years ago

is there any update about this issue ? Menu item is behaving weird with HOC.

pybuche commented 5 years ago

Hi ! Same here, I'm really interested in being able to customize these kind of components. Actually having the problem with a custom Select.Option

No workaround proposed in this thread helped me make it work, I have a working select with empty options....

import React from 'react';
import PropTypes from 'prop-types';
import { Select } from 'antd';

const { Option } = Select;

const PictureOption = ({ label, value, pictureId, ...props }) => (
    <Option label={label} value={value} className="select-item" {...props}>
        <div className="select-item__thumbnail">
            <img src={pictureId} alt="item-img" />
        </div>
        <div className="select-item__name">{label}</div>
    </Option>
);

PictureOption.propTypes = {
    label: PropTypes.string.isRequired,
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
    pictureId: PropTypes.string.isRequired,
};

export default PictureOption;
gibelium commented 5 years ago

I started to use CASL to render UI elements based on user permission. However I ran into the problem that SubMenus are not rendered because the the call to the isRootMenu function fails due to the fact that the props are not correctly propagated to the child elements.

As a workaround I defined the SubMenus as constants and passed in the props 'by hand':

  render() {

    // ### Administration Menu ###
    const AdminMenu = ({ ...props }) => {
      return (
        <Can I="access" on="admin-content">
          <Menu.Divider {...props} />
          <Menu.SubMenu
            {...props}
            title={
              // FIXME: Icon is not rendered... :-/
              <span>
                <Icon type="tools" />
                <span>Administration</span>
              </span>
            }
            // title={<span>Administration</span>}
            key="admin">
            <Menu.Item key="users" tabIndex={0}>
              <Icon type="android" />
              <span>User Administration</span>
            </Menu.Item>
            <Menu.Item key="permissions" tabIndex={0}>
              <Icon type="lock" />
              <span>Permissions</span>
            </Menu.Item>
          </Menu.SubMenu>
        </Can>
      );
    };

    return (
      <Layout id="menu-component-layout">
        <Layout.Sider width="300px" collapsible="false" trigger={null}>
          <Menu theme="dark" mode="inline" defaultSelectedKeys={['user']} defaultOpenKeys={['user', 'config', 'admin']}>
            <AdminMenu />
          </Menu>
        </Layout.Sider>
        <Layout.Content id="menu-component-content">
          <h3>Page containing a side menu</h3>
        </Layout.Content>
      </Layout>
    );
  }

This solution is not very handy but it works for now. However I still have the problem that the title of the SubMenu which includes an icon is not correctly rendered. The icon is missing.

Does anyone has an idea how to fix that?

I created a showcase here: https://github.com/gibelium/meteor-react-antd-casl

GitHub
gibelium/meteor-react-antd-casl
Showcase for the usage of CASL authorization library in a meteor/react environment using the ant-design UI library - gibelium/meteor-react-antd-casl
gotjoshua commented 5 years ago

@gibelium I think that icon rendering deserves its own issue actually. I cloned your repo and tried replacing the icon with a ghost button and the button outline is visible, but the icon also does not render in/on the button...

gibelium commented 5 years ago

@gotjoshua will you create that dedicated issue?

gibelium commented 5 years ago

Setting the default expanded menu items also does not work. My workaround implementation ignores the defaultOpenKeys property of Menu.

Any ideas how to solve that?

iMoses commented 5 years ago

This issue should really be mentioned in the docs, I wasted a full workday researching this bug only to understand this library is unusable for me. Please please please put a warning so other developers won't have to waste so much time figuring out you used an anti-pattern, relaying on React keys, and now you are very fragile to so many HOC or decorators available to the community. This is really a shame. I hope you'll fix it in your next major version.

bodengdd01 commented 4 years ago

is there any update about this issue ? Set Menu defaultOpenKeys not working

marcinlesek commented 4 years ago

Totally something that should be really fast considered as high priority. 🔥

pavolgolias commented 4 years ago

I have similar use-case which I am (probably) not able to implement. I would like to create redux connected component which renders Select with options based on data in redux store. As I dont want to "copy-paste" the same code everywhere, I would like to use this type of component inside Form.getFieldDecorator, but due to using of connect HOC I am not able to do it.

EDIT: I found the solution for my use-case. I was able to create such a component as described above with forwardRef option like this: connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })(Component); This solution is specific for connect HOC, but you should be able to create similar solution by using React.forwardRef.

pavolgolias commented 4 years ago

In addition to previous comments - yes I also think that this could be considered as high priority. After I successfully solved one of my issues (as I described above), now I would need to create Tabs.TabPane component wrapped with my custom component. I have very common use-case -> wrapping component is used to check permissions, so if conditions are met, child component is rendered, otherwise not.

Is there any simple and working workaround for this?

Hokutosei commented 4 years ago

any updates on this please?

bijeshp commented 4 years ago

I found a workaround basically you can pass the rest of the props with <Tabs.TabPane {...props} /> from the wrapped component.

This is showing console warnings Any way to solve this? index.js:1437 Warning: React does not recognize thestaticContextprop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercasestaticcontext` instead. If you accidentally passed it from a parent component, remove it from the DOM element.

index.js:1437 Warning: Invalid value for prop dispatch on

  • tag. Either remove it from the element, or pass a string or number value to keep it in the DOM. For details, see https://fb.me/react-attribute-behavior in li (created by MenuItem) in MenuItem (created by Connect(MenuItem)) in Connect(MenuItem) (created by Context.Consumer) in Trigger (created by Tooltip) in Tooltip (created by Context.Consumer) in Tooltip (created by Context.Consumer) in MenuItem (at FortKnox.js:55) in _FortKnox (created by ConnectFunction) in ConnectFunction (created by Context.Consumer) in withRouter(Connect(_FortKnox)) (at PageSider/index.js:114) in ul (created by DOMWrap) in DOMWrap (created by SubPopupMenu) in SubPopupMenu (created by Connect(SubPopupMenu)) in Connect(SubPopupMenu) (created by Menu) in Provider (created by Menu) `

  • redlapin commented 4 years ago

    same problem... I want to create a permission component as HOC to wrap Menu.Item but antd does not allow this... sad

    marcin-piela commented 4 years ago

    I spent some time on that issue...

    and It's doable 🎉 , you just need to pass rest props to ie. Collapse.Panel (check source code of rc-panel to understand it)

    export const CustomPanel: React.FC = ({ header, children, ...props }) => {
      // do whatever you like
    
      return <Collapse.Panel header={header} {...props}>
        {children}
      </Collapse.Panel>
    };

    and then use it like that:

    <Collapse>
        <CustomPanel key="first" header="Header 1">Body</CustomPanel>
        <CustomPanel key="second" header="Header 2">Body</CustomPanel>
    </Collapse>