Repository: react-materialize/react-materialize
Pull request #825 (2019-04-15 22:14:12)

Add `defaultProps` to Dropdown
# Description

As per the tile this __P.R__ adds `defaultProps` to `Dropdown` component.
Also, it is now possible to initialize `Dropdown` with custom `options`.

I grabbed the chance to enhance the related `story`

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

# How Has This Been Tested?

Added a new spec to check if `Dropdown` correctly initializes itself with default options if none are given.

# Checklist:

- [x] I have commented my code, particularly in hard-to-understand areas
- [x] My changes generate no new warnings
- [x] I have not generated a new package version. (the maintainers will handle that)



Patch:
diff --git a/src/Dropdown.js b/src/Dropdown.js
index 40b026d..c838aed 100644
--- a/src/Dropdown.js
+++ b/src/Dropdown.js
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, { Component, Fragment, Children, cloneElement } from 'react';
 import PropTypes from 'prop-types';
 import idgen from './idgen';
 import cx from 'classnames';
@@ -6,22 +6,24 @@ import cx from 'classnames';
 class Dropdown extends Component {
   constructor(props) {
     super(props);
-    this.idx = 'dropdown_' + idgen();
+    this.idx = props.id || `dropdown${idgen()}`;
     this.renderTrigger = this.renderTrigger.bind(this);
     this.renderItems = this.renderItems.bind(this);
   }
 
   componentDidMount() {
-    const options = this.props.options || {};
+    const { options } = this.props;
 
     if (typeof M !== undefined) {
-      const selector = document.querySelectorAll(this._trigger);
-      this.instance = M.Dropdown.init(selector, options)[0];
+      this.instance = M.Dropdown.init(
+        document.querySelectorAll(this._trigger),
+        options
+      )[0];
     }
   }
 
   componentWillUnmount() {
-    if (typeof M !== undefined) {
+    if (this.instance) {
       this.instance.destroy();
     }
   }
@@ -32,7 +34,7 @@ class Dropdown extends Component {
     delete props.options;
 
     return (
-      <React.Fragment>
+      <Fragment>
         {this.renderTrigger()}
         <ul
           {...props}
@@ -41,14 +43,14 @@ class Dropdown extends Component {
         >
           {this.renderItems()}
         </ul>
-      </React.Fragment>
+      </Fragment>
     );
   }
 
   renderTrigger() {
     const { trigger } = this.props;
 
-    return React.cloneElement(trigger, {
+    return cloneElement(trigger, {
       'data-target': this.idx,
       ref: t => (this._trigger = `[data-target=${this.idx}]`),
       className: cx(trigger.props.className, 'dropdown-trigger')
@@ -58,9 +60,9 @@ class Dropdown extends Component {
   renderItems() {
     const { children } = this.props;
 
-    return React.Children.map(children, element => {
+    return Children.map(children, element => {
       if (element.type.name === 'Divider') {
-        return <li className="divider" tabIndex="-1" />;
+        return <li key={idgen()} className="divider" tabIndex="-1" />;
       } else {
         return <li key={idgen()}>{element}</li>;
       }
@@ -69,6 +71,7 @@ class Dropdown extends Component {
 }
 
 Dropdown.propTypes = {
+  id: PropTypes.string,
   /**
    * The node to trigger the dropdown
    */
@@ -97,4 +100,22 @@ Dropdown.propTypes = {
   })
 };
 
+Dropdown.defaultProps = {
+  options: {
+    alignment: 'left',
+    autoTrigger: true,
+    constrainWidth: true,
+    container: null,
+    coverTrigger: true,
+    closeOnClick: true,
+    hover: false,
+    inDuration: 150,
+    outDuration: 250,
+    onOpenStart: null,
+    onOpenEnd: null,
+    onCloseStart: null,
+    onCloseEnd: null
+  }
+};
+
 export default Dropdown;
diff --git a/stories/Dropdown.stories.js b/stories/Dropdown.stories.js
index 966e345..841e9e1 100644
--- a/stories/Dropdown.stories.js
+++ b/stories/Dropdown.stories.js
@@ -3,6 +3,7 @@ import { storiesOf } from '@storybook/react';
 import Dropdown from '../src/Dropdown';
 import Divider from '../src/Divider';
 import Button from '../src/Button';
+import Icon from '../src/Icon';
 
 const stories = storiesOf('Javascript|Dropdown', module);
 
@@ -15,9 +16,16 @@ stories.addParameters({
 });
 
 stories.add('Default', () => (
-  <Dropdown trigger={<Button>open</Button>}>
-    <a href="#">test</a>
+  <Dropdown trigger={<Button>Drop Me!</Button>}>
+    <a href="#">one</a>
+    <a href="#">two</a>
     <Divider />
-    <a href="#">test</a>
+    <a href="#">three</a>
+    <a href="#">
+      <Icon>view_module</Icon>four
+    </a>
+    <a href="#">
+      <Icon>cloud</Icon> five
+    </a>
   </Dropdown>
 ));


Test patch:
diff --git a/test/DropdownButton.spec.js b/test/DropdownButton.spec.js
index 0db2491..1fa9c41 100644
--- a/test/DropdownButton.spec.js
+++ b/test/DropdownButton.spec.js
@@ -58,6 +58,25 @@ describe('<Dropdown />', () => {
       restore();
     });
 
+    test('with default options if none are given', () => {
+      wrapper = shallow(<Dropdown trigger={<button>Drop me!</button>} />);
+      expect(dropdownInitMock).toHaveBeenCalledWith({
+        alignment: 'left',
+        autoTrigger: true,
+        constrainWidth: true,
+        container: null,
+        coverTrigger: true,
+        closeOnClick: true,
+        hover: false,
+        inDuration: 150,
+        outDuration: 250,
+        onOpenStart: null,
+        onOpenEnd: null,
+        onCloseStart: null,
+        onCloseEnd: null
+      });
+    });
+
     test('handles options prop', () => {
       const options = { constrainWidth: true };
       wrapper = shallow(
diff --git a/test/__snapshots__/DropdownButton.spec.js.snap b/test/__snapshots__/DropdownButton.spec.js.snap
index 84ae3e8..96ad5d2 100644
--- a/test/__snapshots__/DropdownButton.spec.js.snap
+++ b/test/__snapshots__/DropdownButton.spec.js.snap
@@ -4,13 +4,13 @@ exports[`<Dropdown /> renders 1`] = `
 <Fragment>
   <button
     className="dropdown-trigger"
-    data-target="dropdown_mockId"
+    data-target="dropdownmockId"
   >
     Drop me!
   </button>
   <ul
     className="dropdown-content"
-    id="dropdown_mockId"
+    id="dropdownmockId"
   />
 </Fragment>
 `;