Search code examples
reactjsstyled-componentshigher-order-components

React Higher Order Component Warning


I am using a HOC in react to restrict access to a component but I get the following Warning: validateDOMNesting(...): cannot appear as a descendant of . The entire code for the HOC (as requested) is as follows:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import * as selectors from '../../store/selectors';

class AccessRestrictedComponent extends Component {
  static propTypes = {
    auth: PropTypes.object.isRequired,
    mode: PropTypes.string.isRequired,

    useWithList: PropTypes.bool,
    checkItem: PropTypes.bool,

    restrictByRole: PropTypes.bool,
    restrictByMode: PropTypes.bool,
    allowedRoles: PropTypes.array,
    allowedModes: PropTypes.array,
    children: PropTypes.node.isRequired,
  };

  static defaultProps = {
    useWithList: false,
    checkItem: null,
    restrictByRole: false,
    restrictByMode: false,
    allowedRoles: [],
    allowedModes: [],
  };

  render() {
    const { auth, mode, restrictByRole, restrictByMode, allowedRoles, allowedModes, useWithList, checkItem } = this.props;
    const { role } = auth;

    if (useWithList && !checkItem) {
      return  (React.cloneElement(this.props.children, { ...this.props }));
    }

    if (restrictByRole && restrictByMode) {
      // console.log('restricting role and mode ');
      if (allowedRoles.includes(role) && allowedModes.includes(mode)) {
        // console.log(`role: ${role} and mode: ${mode} allowed.`);
        return (React.cloneElement(this.props.children, { ...this.props }));
      } return null;
    }
    if (restrictByRole) {
      // console.log('restricting role ');
      if (allowedRoles.includes(role)) {
        // console.log(`role: ${role} allowed.`);
        return (React.cloneElement(this.props.children, { ...this.props }));
      } return null;
    }
    if (restrictByMode) {
      // console.log('restricting mode ');
      if (allowedModes.includes(mode)) {
        // console.log(`${mode} allowed.`);
        return (React.cloneElement(this.props.children, { ...this.props }));
      } return null;
    }
    // console.log('component unrestricted');
    return (React.cloneElement(this.props.children, { ...this.props }));
  }
}

const mapStateToProps = state => ({
  state,
  auth: selectors.getAuth(state),
  mode: selectors.getMode(state),
});



export default  connect(mapStateToProps)(AccessRestrictedComponent);


the component I am wrapping is a styled component NavButton. I use the HOC like this:

<AccessRestrictedComponent restrictByRole allowedRoles={[userRoles.ADMIN]}>
  <NavButton onClick={() => this.setState({ set: true })}>
    <Icon className="material-icons md-48" color={color}>{'wallpaper'}</Icon>
  </NavButton>
</AccessRestrictedComponent>

and the styled component is like this:

export const NavButton = styled.button`
  display:flex;
  align-content:flex-end;
  align-self:flex-end;
  background: none;
  border: none;
  &:hover,
  &:focus{
    ${Icon}{
      cursor: pointer;
      color: ${props => props.theme.theme === themeTypes.LIGHT ? colors.SLATE_BLUE_50 : colors.lightBlue};
      border-radius: 4px;
      outline: none;
      background: linear-gradient(${colors.shuttleGrey}, ${colors.shuttleGrey}) padding-box,
                  repeating-linear-gradient(-45deg,
                    ${colors.submarine} 0, ${colors.submarine} 25%, transparent 0, transparent 50%
                  ) 0 / 8px 8px;
    }
  }
`;

I do not understand this warning because I am not nesting a button but when I inspect the UI with dev tools there are two buttons nested. Does anyone know what could be causing this? Is it styled components? the HOC, something else maybe? I guess a simple solution is to change the styled component to something other than a button but I feel this should be valid code. Anyways, any suggestions greatly appreciated.


Solution

  • The error happens because when you clone the element, you pass children to cloned child as props. So your parent clones the button and sends another button as props to your cloned button.

    Fix is simple, don't send children as merged props.

    render() {
      const { children, ...rest } = this.props;
      // ...rest
      return React.cloneElement(children, { ...rest });
    
      //...rest
    }
    

    This way you wont send children as props to children again.

    Note: You should replace all your React.cloneElement() calls