Pomax / react-onclickoutside

An onClickOutside wrapper for React components
MIT License
1.83k stars 187 forks source link

DOM Attributes are not lowercase issue #244

Closed Rikusor closed 7 years ago

Rikusor commented 7 years ago

Hi,

I get following warning using this library;

main.bundle.js:3101 
Warning: React does not recognize the `enableOnClickOutside` prop on a DOM element.
If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `enableonclickoutside` instead. 
If you accidentally passed it from a parent component, remove it from the DOM element.

This repeats for following attributes;

Is there something to fix this Warning?

Andarist commented 7 years ago

How do u use react-onclickoutside? Do you wrap a DOM component?

Rikusor commented 7 years ago

Hi,

Below is my component.

import React from 'react';
import PropTypes from 'prop-types';
import pull from 'lodash/pull';
import onClickOutside from 'react-onclickoutside'
import cxs from '../_helpers/classnames';
import DropdownItem from './DropdownItem';
import * as s from './styles';

class Dropdown extends React.Component {
  constructor(props) {
    super(props);
    const selected = props.items.filter(item => item.selected).map(item => item.value);
    this.state = {
      opened: false,
      selected,
    };
    this.valueChange = this.valueChange.bind(this);
    this.toggle = this.toggle.bind(this);
    this.setWrapperRef = this.setWrapperRef.bind(this);
  }
  setWrapperRef(node) {
    this.wrapperRef = node;
  }
  handleClickOutside() {
    this.close();
  }
  valueChange(value) {
    let updatedSelected;
    if (this.props.multiselect) {
      const selected = this.state.selected;
      updatedSelected = selected.includes(value) ? pull(selected, value) : selected.concat(value);
    } else {
      updatedSelected = [value];
    }
    this.setState({
      selected: updatedSelected,
    });
    this.props.onChange(updatedSelected);
  }
  close() {
    this.setState({
      opened: false,
    });
  }
  toggle(e) {
    e.preventDefault();
    this.setState({
      opened: !this.state.opened,
    });
  }
  render() {
    const {
      closeAfterClick,
      items,
      multiselect,
      onChange,
      ...props
    } = this.props;
    const {
      opened,
      selected,
    } = this.state;
    const classNames = cxs({
      dropdown: true,
      dropdownOpened: opened,
    }, s);
    const itemsClassNames = cxs({
      dropdownItems: true,
    }, s);
    const heading = items.filter(item => selected.includes(item.value)).map(item => item.name);
    const maxHeight = opened ? items.length * 64 : 0;
    const multiplyBy = Math.floor(items.length / 4);
    const duration = 150 + (50 * multiplyBy);
    return (
      <div className={classNames} {...props} ref={this.setWrapperRef}>
        <a className={s.dropdownHeading} href="#" onClick={this.toggle}>
          { selected.length ? heading.join('; ') : 'Select option' }
        </a>
        <ul className={itemsClassNames} style={{ maxHeight: `${maxHeight}px`, transitionDuration: `${duration}ms` }}>
          { items.map((item, idx) => {
            const delay = opened ? (duration / items.length) * idx : 0;
            return (
              <DropdownItem
                key={item.value}
                onChange={() => {
                  this.valueChange(item.value);
                  if (closeAfterClick) {
                    this.close();
                  }
                }}
                multiselect={multiselect}
                selected={selected.includes(item.value)}
                style={{ 'transitionDelay': `${delay}ms` }}
              >
                {item.name}
              </DropdownItem>
            );
          })}
        </ul>
      </div>
    );
  }
}

Dropdown.propTypes = {
  closeAfterClick: PropTypes.bool,
  items: PropTypes.arrayOf(PropTypes.object).isRequired,
  multiselect: PropTypes.bool,
  /**
  * The change handler that will receive the updated values as it's only param
  */
  onChange: PropTypes.func.isRequired,
};

Dropdown.defaultProps = {
  closeAfterClick: true,
  items: [],
};

export default onClickOutside(Dropdown);

Best regards, Riku

Andarist commented 7 years ago

The problem is here - <div className={classNames} {...props} ref={this.setWrapperRef}>

You shouldn't passthrough all props, because react-onclickoutside provide (inject into your component, so they can be used) props for you.

You should filter them out here:

    const {
      closeAfterClick,
      items,
      multiselect,
      onChange,
      ...props
    } = this.props;