Ant-design: A List of `antd`'s components that cannot work with HOC

Created on 14 Feb 2017  ·  65Comments  ·  Source: ant-design/ant-design

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

Inactive ❓FAQ 🗣 Discussion

Most helpful comment

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.

All 65 comments

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.

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

I don't think this is a good idea.

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.

@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.

@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?

@benjycui I understand.

Anyway, it is not blocking after all.

Thank you for taking time answering.

@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!

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.

Please find a solution for this in v3.

Can be resolved after this package released (maybe).

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?

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.

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.

Any updates on this?

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.

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

I found a workaround basically you can pass the rest of the props with from the wrapped component.

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.

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.

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

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

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>
  )
}

Same issue than @ncknuna.

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

@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>
  )
}

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>
 );
}

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:

  • pass down props
  • add eventKey and subMenuKey, which should be unique

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.

  • 1 for ability to embed antd components into custom components. This is make or break for us

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.

@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

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

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"

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?

Any update on this?

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

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;

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
Showcase for the usage of CASL authorization library in a meteor/react environment using the ant-design UI library - gibelium/meteor-react-antd-casl

@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...

@gotjoshua will you create that dedicated issue?

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?

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.

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

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

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.

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?

any updates on this please?

I found a workaround basically you can pass the rest of the props with 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)
    `

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

    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>
    

    Would be awesome to see this solution in the 4.0 release!

    This is absolutely an high priority.
    Literally makes those components unusable when special behaviors are required (sometimes even simple ones, like authorization).
    For example, I'm trying to create a dynamically-loaded menu, so I literally display a disabled Menu.Item with a spinner as its name until the data arrives.
    That's far from optimal.

    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)

    While it's a great solution (really! am using it in my projects) you do lose some functionality.
    I.e. If your <SubMenu> is not a direct child of <Menu>, defaultOpenKeys will not work. 😞

    Just encountered this issue FYI.

    Definitely needs acknowledgement in the documentation. Seriously frustrating to come across this issue (among quite a few other minor ones) that are making me seriously reconsider using AntDesign in my projects.

    I can't believe there is still no fix for that.

    This really needs to be fixed. Most developers dont just use components straight out of the box. This means that we might want to integrate more functionality that cannot be otherwise added trhough the api provided. I would not call HOC's a non common or low priority use case. It is fundamental to the compositional nature of React.

    Please, fix this, and while its being fixed add the information, as well as the workarounds people have found here into your official docs.

    Please, fix this, and while its being fixed add the information, as well as the workarounds people have found here into your official docs.

    Maintainers are open to PR, you can also send a PR for a docs update if you want and have the time to do it.

    @afc163

    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>
    

    Can someone please explain what is happenning below. Also trying to wrap Panel inside a component.

    If I use this code: :

     <PersonalInfoPanel
                    header="header"
                    key={"personalInfo" + i}
                    extra={genExtra()}
                />
    

    inside PersonalInfoPanel:

    function PersonalInfoPanel(props, ref) {
        return (
            <Panel header={props.header} key={props.key} extra={props.extra}>
    ...
    

    It doesn't work.

    But as soon as I use this instead:

    function PersonalInfoPanel(props, ref) {
        return (
            <Panel {...props}>
    ...
    

    it starts to work.
    Can someone explain why?

     <Panel {...props}>
    ...
    

    it starts to work.

    Can someone explain why?

    As I understand it, the parent Collapse will need to set props other than header, key, and extra (from your non working example). These props from the parent Collapse need to be explicitly put onto the Panel Component within your custom component.

    I guess you could use the React Inspector to learn all of the possible props that will change and pass them in one by one, but the ...props syntax makes sure that anything the parent wants to add to its child Panel will be attached (including but not limited to the ones you are explicitly needing to set)

    This issue need to fix.
    MANY (or.. almost) developers wants to use library to custom Components.

    Example)
    This code is not working. Because Menu and Menu Item need Opaque props.

          <Menu>
            { menus.map((item) => {
              if (item.to) {
                return <Menu.Item title={item.title} />;
              }
              // some codes...
              return null;
            })}
          </Menu>
    

    @moonjoungyoung @adamerose
    did you read the thread ?
    You have to pass rest props to the inner antd component to make it work.

    Here's what worked for me to get custom components + conditional rendering for react-router NavLinks inside an antd Menu, really though this needs a fix - I wasted so much time before finding this thread.

    _edit- Nevermind, selectedKeys doesn't work properly_

    const Nav = withRouter(() => {
      const auth = store.isAuthenticated;
    
      return (
        <Menu selectedKeys={[history.location.pathname]} mode="horizontal">
          <NavItem id="/">Home</NavItem>
          {auth && <NavItem id="/create">Create Post</NavItem>}
          {!auth && <NavItem id="/sign-in">Sign In</NavItem>}
          {!auth && <NavItem id="/register">Register</NavItem>}
        </Menu>
      );
    });
    
    const NavItem = ({ ...props }) => (
      <Menu.Item {...props} key={props.id}>
        <NavLink exact to={props.id}>
          {props.children}
        </NavLink>
      </Menu.Item>
    );
    

    @moonjoungyoung @adamerose
    did you read the thread ?
    You have to pass rest props to the inner antd component to make it work.

    Can you take a look at my example above? I thought I applied every work around in this thread but it still doesn't work properly. I'm passing the props down and spreading them on the Menu.Item but it still won't highlight when active, and the component tree looks like this
    image

    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)

    In my case, I have to put "header ={calculated_header}" behind "{...props}". If I don't do this, the header of panel doesn't show. I think "{...props}" which appears later in the sequence overwrites the "header" information. When I put "{...props}" at the beginning, it works. In that case, I think the "header" which appear later overwrites the "header" information in the props.

    My adaptation to @marcin-piela response is as follows:

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

    Was this page helpful?
    0 / 5 - 0 ratings

    Related issues

    plandem picture plandem  ·  3Comments

    drcmda picture drcmda  ·  3Comments

    AhmedSayed77 picture AhmedSayed77  ·  3Comments

    xtznhzxdev picture xtznhzxdev  ·  3Comments

    longhuasishen picture longhuasishen  ·  3Comments