about summary refs log tree commit diff
path: root/app/assets/javascripts/components/features/compose
diff options
context:
space:
mode:
authorYamagishi Kazutoshi <ykzts@desire.sh>2017-04-22 03:05:35 +0900
committerEugen <eugen@zeonfederated.com>2017-04-21 20:05:35 +0200
commit1948f9e767c5c8f7cb52337ce777a61b5ad1a599 (patch)
tree4d5f3549f6e5fd88340bb3543e1eb2eafc47952c /app/assets/javascripts/components/features/compose
parent27ea2a88c12835b491ea5537f934983470faf781 (diff)
Remove deprecated features at React v15.5 (#1905)
* Remove deprecated features at React v15.5

- [x] React.PropTypes
- [x] react-addons-pure-render-mixin
- [x] react-addons-test-utils

* Uncommented out & Add browserify_rails options

* re-add react-addons-shallow

* Fix syntax error from resolve conflicts

* follow up 59a77923b368d48c590cd9f4a0c6b73ce972d33f
Diffstat (limited to 'app/assets/javascripts/components/features/compose')
-rw-r--r--app/assets/javascripts/components/features/compose/components/character_counter.jsx20
-rw-r--r--app/assets/javascripts/components/features/compose/components/compose_form.jsx93
-rw-r--r--app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx26
-rw-r--r--app/assets/javascripts/components/features/compose/components/navigation_bar.jsx14
-rw-r--r--app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx43
-rw-r--r--app/assets/javascripts/components/features/compose/components/reply_indicator.jsx36
-rw-r--r--app/assets/javascripts/components/features/compose/components/search.jsx43
-rw-r--r--app/assets/javascripts/components/features/compose/components/search_results.jsx15
-rw-r--r--app/assets/javascripts/components/features/compose/components/text_icon_button.jsx27
-rw-r--r--app/assets/javascripts/components/features/compose/components/upload_button.jsx35
-rw-r--r--app/assets/javascripts/components/features/compose/components/upload_form.jsx20
-rw-r--r--app/assets/javascripts/components/features/compose/components/upload_progress.jsx18
-rw-r--r--app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx19
-rw-r--r--app/assets/javascripts/components/features/compose/index.jsx26
14 files changed, 229 insertions, 206 deletions
diff --git a/app/assets/javascripts/components/features/compose/components/character_counter.jsx b/app/assets/javascripts/components/features/compose/components/character_counter.jsx
index fc64f94a5..b1e74b4de 100644
--- a/app/assets/javascripts/components/features/compose/components/character_counter.jsx
+++ b/app/assets/javascripts/components/features/compose/components/character_counter.jsx
@@ -1,20 +1,13 @@
-import PureRenderMixin from 'react-addons-pure-render-mixin';
+import PropTypes from 'prop-types';
 
-const CharacterCounter = React.createClass({
-
-  propTypes: {
-    text: React.PropTypes.string.isRequired,
-    max: React.PropTypes.number.isRequired
-  },
-
-  mixins: [PureRenderMixin],
+class CharacterCounter extends React.PureComponent {
 
   checkRemainingText (diff) {
     if (diff <= 0) {
       return <span style={{ fontSize: '16px', cursor: 'default', color: '#ff5050' }}>{diff}</span>;
     }
     return <span style={{ fontSize: '16px', cursor: 'default' }}>{diff}</span>;
-  },
+  }
 
   render () {
     const diff = this.props.max - this.props.text.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "_").length;
@@ -22,6 +15,11 @@ const CharacterCounter = React.createClass({
     return this.checkRemainingText(diff);
   }
 
-});
+}
+
+CharacterCounter.propTypes = {
+  text: PropTypes.string.isRequired,
+  max: PropTypes.number.isRequired
+}
 
 export default CharacterCounter;
diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx
index e6e68351e..b8e8ed5ef 100644
--- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx
+++ b/app/assets/javascripts/components/features/compose/components/compose_form.jsx
@@ -1,7 +1,7 @@
 import CharacterCounter from './character_counter';
 import Button from '../../../components/button';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
 import ReplyIndicatorContainer from '../containers/reply_indicator_container';
 import AutosuggestTextarea from '../../../components/autosuggest_textarea';
 import { debounce } from 'react-decoration';
@@ -22,67 +22,53 @@ const messages = defineMessages({
   publish: { id: 'compose_form.publish', defaultMessage: 'Toot' }
 });
 
-const ComposeForm = React.createClass({
-
-  propTypes: {
-    intl: React.PropTypes.object.isRequired,
-    text: React.PropTypes.string.isRequired,
-    suggestion_token: React.PropTypes.string,
-    suggestions: ImmutablePropTypes.list,
-    spoiler: React.PropTypes.bool,
-    privacy: React.PropTypes.string,
-    spoiler_text: React.PropTypes.string,
-    focusDate: React.PropTypes.instanceOf(Date),
-    preselectDate: React.PropTypes.instanceOf(Date),
-    is_submitting: React.PropTypes.bool,
-    is_uploading: React.PropTypes.bool,
-    me: React.PropTypes.number,
-    needsPrivacyWarning: React.PropTypes.bool,
-    mentionedDomains: React.PropTypes.array.isRequired,
-    onChange: React.PropTypes.func.isRequired,
-    onSubmit: React.PropTypes.func.isRequired,
-    onClearSuggestions: React.PropTypes.func.isRequired,
-    onFetchSuggestions: React.PropTypes.func.isRequired,
-    onSuggestionSelected: React.PropTypes.func.isRequired,
-    onChangeSpoilerText: React.PropTypes.func.isRequired,
-    onPaste: React.PropTypes.func.isRequired,
-    onPickEmoji: React.PropTypes.func.isRequired
-  },
-
-  mixins: [PureRenderMixin],
+class ComposeForm extends React.PureComponent {
+
+  constructor (props, context) {
+    super(props, context);
+    this.handleChange = this.handleChange.bind(this);
+    this.handleKeyDown = this.handleKeyDown.bind(this);
+    this.handleSubmit = this.handleSubmit.bind(this);
+    this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this);
+    this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this);
+    this.onSuggestionSelected = this.onSuggestionSelected.bind(this);
+    this.handleChangeSpoilerText = this.handleChangeSpoilerText.bind(this);
+    this.setAutosuggestTextarea = this.setAutosuggestTextarea.bind(this);
+    this.handleEmojiPick = this.handleEmojiPick.bind(this);
+  }
 
   handleChange (e) {
     this.props.onChange(e.target.value);
-  },
+  }
 
   handleKeyDown (e) {
     if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
       this.props.onSubmit();
     }
-  },
+  }
 
   handleSubmit () {
     this.autosuggestTextarea.textarea.style.height = "auto";
     this.props.onSubmit();
-  },
+  }
 
   onSuggestionsClearRequested () {
     this.props.onClearSuggestions();
-  },
+  }
 
   @debounce(500)
   onSuggestionsFetchRequested (token) {
     this.props.onFetchSuggestions(token);
-  },
+  }
 
   onSuggestionSelected (tokenStart, token, value) {
     this._restoreCaret = null;
     this.props.onSuggestionSelected(tokenStart, token, value);
-  },
+  }
 
   handleChangeSpoilerText (e) {
     this.props.onChangeSpoilerText(e.target.value);
-  },
+  }
 
   componentWillReceiveProps (nextProps) {
     // If this is the update where we've finished uploading,
@@ -90,7 +76,7 @@ const ComposeForm = React.createClass({
     if (!nextProps.is_uploading && this.props.is_uploading) {
       this._restoreCaret = this.autosuggestTextarea.textarea.selectionStart;
     }
-  },
+  }
 
   componentDidUpdate (prevProps) {
     // This statement does several things:
@@ -117,17 +103,17 @@ const ComposeForm = React.createClass({
       this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
       this.autosuggestTextarea.textarea.focus();
     }
-  },
+  }
 
   setAutosuggestTextarea (c) {
     this.autosuggestTextarea = c;
-  },
+  }
 
   handleEmojiPick (data) {
     const position     = this.autosuggestTextarea.textarea.selectionStart;
     this._restoreCaret = position + data.shortname.length + 1;
     this.props.onPickEmoji(position, data);
-  },
+  }
 
   render () {
     const { intl, needsPrivacyWarning, mentionedDomains, onPaste } = this.props;
@@ -207,6 +193,31 @@ const ComposeForm = React.createClass({
     );
   }
 
-});
+}
+
+ComposeForm.propTypes = {
+  intl: PropTypes.object.isRequired,
+  text: PropTypes.string.isRequired,
+  suggestion_token: PropTypes.string,
+  suggestions: ImmutablePropTypes.list,
+  spoiler: PropTypes.bool,
+  privacy: PropTypes.string,
+  spoiler_text: PropTypes.string,
+  focusDate: PropTypes.instanceOf(Date),
+  preselectDate: PropTypes.instanceOf(Date),
+  is_submitting: PropTypes.bool,
+  is_uploading: PropTypes.bool,
+  me: PropTypes.number,
+  needsPrivacyWarning: PropTypes.bool,
+  mentionedDomains: PropTypes.array.isRequired,
+  onChange: PropTypes.func.isRequired,
+  onSubmit: PropTypes.func.isRequired,
+  onClearSuggestions: PropTypes.func.isRequired,
+  onFetchSuggestions: PropTypes.func.isRequired,
+  onSuggestionSelected: PropTypes.func.isRequired,
+  onChangeSpoilerText: PropTypes.func.isRequired,
+  onPaste: PropTypes.func.isRequired,
+  onPickEmoji: PropTypes.func.isRequired
+};
 
 export default injectIntl(ComposeForm);
diff --git a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx
index 36e97df41..77117b7e4 100644
--- a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx
+++ b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx
@@ -1,6 +1,6 @@
 import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
 import EmojiPicker from 'emojione-picker';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
+import PropTypes from 'prop-types';
 import { defineMessages, injectIntl } from 'react-intl';
 
 const messages = defineMessages({
@@ -19,23 +19,22 @@ const style = {
   top: '5px'
 };
 
-const EmojiPickerDropdown = React.createClass({
+class EmojiPickerDropdown extends React.PureComponent {
 
-  propTypes: {
-    intl: React.PropTypes.object.isRequired,
-    onPickEmoji: React.PropTypes.func.isRequired
-  },
-
-  mixins: [PureRenderMixin],
+  constructor (props, context) {
+    super(props, context);
+    this.setRef = this.setRef.bind(this);
+    this.handleChange = this.handleChange.bind(this);
+  }
 
   setRef (c) {
     this.dropdown = c;
-  },
+  }
 
   handleChange (data) {
     this.dropdown.hide();
     this.props.onPickEmoji(data);
-  },
+  }
 
   render () {
     const { intl } = this.props;
@@ -53,6 +52,11 @@ const EmojiPickerDropdown = React.createClass({
     );
   }
 
-});
+}
+
+EmojiPickerDropdown.propTypes = {
+  intl: PropTypes.object.isRequired,
+  onPickEmoji: PropTypes.func.isRequired
+};
 
 export default injectIntl(EmojiPickerDropdown);
diff --git a/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx b/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx
index 1a748a23c..f4c45278c 100644
--- a/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx
+++ b/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx
@@ -1,4 +1,3 @@
-import PureRenderMixin from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import Avatar from '../../../components/avatar';
 import IconButton from '../../../components/icon_button';
@@ -7,12 +6,7 @@ import Permalink from '../../../components/permalink';
 import { FormattedMessage } from 'react-intl';
 import { Link } from 'react-router';
 
-const NavigationBar = React.createClass({
-  propTypes: {
-    account: ImmutablePropTypes.map.isRequired
-  },
-
-  mixins: [PureRenderMixin],
+class NavigationBar extends React.PureComponent {
 
   render () {
     return (
@@ -27,6 +21,10 @@ const NavigationBar = React.createClass({
     );
   }
 
-});
+}
+
+NavigationBar.propTypes = {
+  account: ImmutablePropTypes.map.isRequired
+};
 
 export default NavigationBar;
diff --git a/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx
index de8942d4d..6a80cf7a2 100644
--- a/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx
+++ b/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx
@@ -1,4 +1,4 @@
-import PureRenderMixin from 'react-addons-pure-render-mixin';
+import PropTypes from 'prop-types';
 import { injectIntl, defineMessages } from 'react-intl';
 import IconButton from '../../../components/icon_button';
 
@@ -19,51 +19,48 @@ const iconStyle = {
   height: null
 };
 
-const PrivacyDropdown = React.createClass({
+class PrivacyDropdown extends React.PureComponent {
 
-  propTypes: {
-    value: React.PropTypes.string.isRequired,
-    onChange: React.PropTypes.func.isRequired,
-    intl: React.PropTypes.object.isRequired
-  },
-
-  getInitialState () {
-    return {
+  constructor (props, context) {
+    super(props, context);
+    this.state = {
       open: false
     };
-  },
-
-  mixins: [PureRenderMixin],
+    this.handleToggle = this.handleToggle.bind(this);
+    this.handleClick = this.handleClick.bind(this);
+    this.onGlobalClick = this.onGlobalClick.bind(this);
+    this.setRef = this.setRef.bind(this);
+  }
 
   handleToggle () {
     this.setState({ open: !this.state.open });
-  },
+  }
 
   handleClick (value, e) {
     e.preventDefault();
     this.setState({ open: false });
     this.props.onChange(value);
-  },
+  }
 
   onGlobalClick (e) {
     if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
       this.setState({ open: false });
     }
-  },
+  }
 
   componentDidMount () {
     window.addEventListener('click', this.onGlobalClick);
     window.addEventListener('touchstart', this.onGlobalClick);
-  },
+  }
 
   componentWillUnmount () {
     window.removeEventListener('click', this.onGlobalClick);
     window.removeEventListener('touchstart', this.onGlobalClick);
-  },
+  }
 
   setRef (c) {
     this.node = c;
-  },
+  }
 
   render () {
     const { value, onChange, intl } = this.props;
@@ -96,6 +93,12 @@ const PrivacyDropdown = React.createClass({
     );
   }
 
-});
+}
+
+PrivacyDropdown.propTypes = {
+  value: PropTypes.string.isRequired,
+  onChange: PropTypes.func.isRequired,
+  intl: PropTypes.object.isRequired
+};
 
 export default injectIntl(PrivacyDropdown);
diff --git a/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx b/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx
index 11a89449e..784f08284 100644
--- a/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx
+++ b/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx
@@ -1,5 +1,5 @@
-import PureRenderMixin from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
 import Avatar from '../../../components/avatar';
 import IconButton from '../../../components/icon_button';
 import DisplayName from '../../../components/display_name';
@@ -10,30 +10,24 @@ const messages = defineMessages({
   cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' }
 });
 
-const ReplyIndicator = React.createClass({
+class ReplyIndicator extends React.PureComponent {
 
-  contextTypes: {
-    router: React.PropTypes.object
-  },
-
-  propTypes: {
-    status: ImmutablePropTypes.map,
-    onCancel: React.PropTypes.func.isRequired,
-    intl: React.PropTypes.object.isRequired
-  },
-
-  mixins: [PureRenderMixin],
+  constructor (props, context) {
+    super(props, context);
+    this.handleClick = this.handleClick.bind(this);
+    this.handleAccountClick = this.handleAccountClick.bind(this);
+  }
 
   handleClick () {
     this.props.onCancel();
-  },
+  }
 
   handleAccountClick (e) {
     if (e.button === 0) {
       e.preventDefault();
       this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
     }
-  },
+  }
 
   render () {
     const { status, intl } = this.props;
@@ -60,6 +54,16 @@ const ReplyIndicator = React.createClass({
     );
   }
 
-});
+}
+
+ReplyIndicator.contextTypes = {
+  router: PropTypes.object
+};
+
+ReplyIndicator.propTypes = {
+  status: ImmutablePropTypes.map,
+  onCancel: PropTypes.func.isRequired,
+  intl: PropTypes.object.isRequired
+};
 
 export default injectIntl(ReplyIndicator);
diff --git a/app/assets/javascripts/components/features/compose/components/search.jsx b/app/assets/javascripts/components/features/compose/components/search.jsx
index 9ca1f5dc5..7b025341b 100644
--- a/app/assets/javascripts/components/features/compose/components/search.jsx
+++ b/app/assets/javascripts/components/features/compose/components/search.jsx
@@ -1,48 +1,43 @@
-import PureRenderMixin from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 
 const messages = defineMessages({
   placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }
 });
 
-const Search = React.createClass({
+class Search extends React.PureComponent {
 
-  propTypes: {
-    value: React.PropTypes.string.isRequired,
-    submitted: React.PropTypes.bool,
-    onChange: React.PropTypes.func.isRequired,
-    onSubmit: React.PropTypes.func.isRequired,
-    onClear: React.PropTypes.func.isRequired,
-    onShow: React.PropTypes.func.isRequired,
-    intl: React.PropTypes.object.isRequired
-  },
-
-  mixins: [PureRenderMixin],
+  constructor (props, context) {
+    super(props, context);
+    this.handleChange = this.handleChange.bind(this);
+    this.handleKeyDown = this.handleKeyDown.bind(this);
+    this.handleFocus = this.handleFocus.bind(this);
+  }
 
   handleChange (e) {
     this.props.onChange(e.target.value);
-  },
+  }
 
   handleClear (e) {
     e.preventDefault();
     this.props.onClear();
-  },
+  }
 
   handleKeyDown (e) {
     if (e.key === 'Enter') {
       e.preventDefault();
       this.props.onSubmit();
     }
-  },
+  }
 
   noop () {
 
-  },
+  }
 
   handleFocus () {
     this.props.onShow();
-  },
+  }
 
   render () {
     const { intl, value, submitted } = this.props;
@@ -68,6 +63,16 @@ const Search = React.createClass({
     );
   }
 
-});
+}
+
+Search.propTypes = {
+  value: PropTypes.string.isRequired,
+  submitted: PropTypes.bool,
+  onChange: PropTypes.func.isRequired,
+  onSubmit: PropTypes.func.isRequired,
+  onClear: PropTypes.func.isRequired,
+  onShow: PropTypes.func.isRequired,
+  intl: PropTypes.object.isRequired
+};
 
 export default injectIntl(Search);
diff --git a/app/assets/javascripts/components/features/compose/components/search_results.jsx b/app/assets/javascripts/components/features/compose/components/search_results.jsx
index d0064f1b9..00bfd1786 100644
--- a/app/assets/javascripts/components/features/compose/components/search_results.jsx
+++ b/app/assets/javascripts/components/features/compose/components/search_results.jsx
@@ -1,17 +1,10 @@
-import PureRenderMixin from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import AccountContainer from '../../../containers/account_container';
 import StatusContainer from '../../../containers/status_container';
 import { Link } from 'react-router';
 
-const SearchResults = React.createClass({
-
-  propTypes: {
-    results: ImmutablePropTypes.map.isRequired
-  },
-
-  mixins: [PureRenderMixin],
+class SearchResults extends React.PureComponent {
 
   render () {
     const { results } = this.props;
@@ -63,6 +56,10 @@ const SearchResults = React.createClass({
     );
   }
 
-});
+}
+
+SearchResults.propTypes = {
+  results: ImmutablePropTypes.map.isRequired
+};
 
 export default SearchResults;
diff --git a/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx b/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx
index e3ac63d87..edf413e87 100644
--- a/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx
+++ b/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx
@@ -1,20 +1,16 @@
-import PureRenderMixin from 'react-addons-pure-render-mixin';
+import PropTypes from 'prop-types';
 
-const TextIconButton = React.createClass({
+class TextIconButton extends React.PureComponent {
 
-  propTypes: {
-    label: React.PropTypes.string.isRequired,
-    title: React.PropTypes.string,
-    active: React.PropTypes.bool,
-    onClick: React.PropTypes.func.isRequired
-  },
-
-  mixins: [PureRenderMixin],
+  constructor (props, context) {
+    super(props, context);
+    this.handleClick = this.handleClick.bind(this);
+  }
 
   handleClick (e) {
     e.preventDefault();
     this.props.onClick();
-  },
+  }
 
   render () {
     const { label, title, active } = this.props;
@@ -26,6 +22,13 @@ const TextIconButton = React.createClass({
     );
   }
 
-});
+}
+
+TextIconButton.propTypes = {
+  label: PropTypes.string.isRequired,
+  title: PropTypes.string,
+  active: PropTypes.bool,
+  onClick: PropTypes.func.isRequired
+};
 
 export default TextIconButton;
diff --git a/app/assets/javascripts/components/features/compose/components/upload_button.jsx b/app/assets/javascripts/components/features/compose/components/upload_button.jsx
index 2ba0e8fd2..64b36a4df 100644
--- a/app/assets/javascripts/components/features/compose/components/upload_button.jsx
+++ b/app/assets/javascripts/components/features/compose/components/upload_button.jsx
@@ -1,5 +1,5 @@
-import PureRenderMixin from 'react-addons-pure-render-mixin';
 import IconButton from '../../../components/icon_button';
+import PropTypes from 'prop-types';
 import { defineMessages, injectIntl } from 'react-intl';
 
 const messages = defineMessages({
@@ -11,31 +11,28 @@ const iconStyle = {
   height: null
 };
 
-const UploadButton = React.createClass({
+class UploadButton extends React.PureComponent {
 
-  propTypes: {
-    disabled: React.PropTypes.bool,
-    onSelectFile: React.PropTypes.func.isRequired,
-    style: React.PropTypes.object,
-    resetFileKey: React.PropTypes.number,
-    intl: React.PropTypes.object.isRequired
-  },
-
-  mixins: [PureRenderMixin],
+  constructor (props, context) {
+    super(props, context);
+    this.handleChange = this.handleChange.bind(this);
+    this.handleClick = this.handleClick.bind(this);
+    this.setRef = this.setRef.bind(this);
+  }
 
   handleChange (e) {
     if (e.target.files.length > 0) {
       this.props.onSelectFile(e.target.files);
     }
-  },
+  }
 
   handleClick () {
     this.fileElement.click();
-  },
+  }
 
   setRef (c) {
     this.fileElement = c;
-  },
+  }
 
   render () {
     const { intl, resetFileKey, disabled } = this.props;
@@ -48,6 +45,14 @@ const UploadButton = React.createClass({
     );
   }
 
-});
+}
+
+UploadButton.propTypes = {
+  disabled: PropTypes.bool,
+  onSelectFile: PropTypes.func.isRequired,
+  style: PropTypes.object,
+  resetFileKey: PropTypes.number,
+  intl: PropTypes.object.isRequired
+};
 
 export default injectIntl(UploadButton);
diff --git a/app/assets/javascripts/components/features/compose/components/upload_form.jsx b/app/assets/javascripts/components/features/compose/components/upload_form.jsx
index 77590d90d..f28944ad5 100644
--- a/app/assets/javascripts/components/features/compose/components/upload_form.jsx
+++ b/app/assets/javascripts/components/features/compose/components/upload_form.jsx
@@ -1,5 +1,5 @@
-import PureRenderMixin from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
 import IconButton from '../../../components/icon_button';
 import { defineMessages, injectIntl } from 'react-intl';
 import UploadProgressContainer from '../containers/upload_progress_container';
@@ -9,15 +9,7 @@ const messages = defineMessages({
   undo: { id: 'upload_form.undo', defaultMessage: 'Undo' }
 });
 
-const UploadForm = React.createClass({
-
-  propTypes: {
-    media: ImmutablePropTypes.list.isRequired,
-    onRemoveFile: React.PropTypes.func.isRequired,
-    intl: React.PropTypes.object.isRequired
-  },
-
-  mixins: [PureRenderMixin],
+class UploadForm extends React.PureComponent {
 
   render () {
     const { intl, media } = this.props;
@@ -42,6 +34,12 @@ const UploadForm = React.createClass({
     );
   }
 
-});
+}
+
+UploadForm.propTypes = {
+  media: ImmutablePropTypes.list.isRequired,
+  onRemoveFile: PropTypes.func.isRequired,
+  intl: PropTypes.object.isRequired
+};
 
 export default injectIntl(UploadForm);
diff --git a/app/assets/javascripts/components/features/compose/components/upload_progress.jsx b/app/assets/javascripts/components/features/compose/components/upload_progress.jsx
index 86ffbf936..a04edb97d 100644
--- a/app/assets/javascripts/components/features/compose/components/upload_progress.jsx
+++ b/app/assets/javascripts/components/features/compose/components/upload_progress.jsx
@@ -1,15 +1,8 @@
-import PureRenderMixin from 'react-addons-pure-render-mixin';
+import PropTypes from 'prop-types';
 import { Motion, spring } from 'react-motion';
 import { FormattedMessage } from 'react-intl';
 
-const UploadProgress = React.createClass({
-
-  propTypes: {
-    active: React.PropTypes.bool,
-    progress: React.PropTypes.number
-  },
-
-  mixins: [PureRenderMixin],
+class UploadProgress extends React.PureComponent {
 
   render () {
     const { active, progress } = this.props;
@@ -39,6 +32,11 @@ const UploadProgress = React.createClass({
     );
   }
 
-});
+}
+
+UploadProgress.propTypes = {
+  active: PropTypes.bool,
+  progress: PropTypes.number
+};
 
 export default UploadProgress;
diff --git a/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx b/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx
index 074b568f4..c83598a7d 100644
--- a/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx
+++ b/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx
@@ -1,4 +1,5 @@
 import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
 import TextIconButton from '../components/text_icon_button';
 import { changeComposeSensitivity } from '../../../actions/compose';
 import { Motion, spring } from 'react-motion';
@@ -21,14 +22,7 @@ const mapDispatchToProps = dispatch => ({
 
 });
 
-const SensitiveButton = React.createClass({
-
-  propTypes: {
-    visible: React.PropTypes.bool,
-    active: React.PropTypes.bool,
-    onClick: React.PropTypes.func.isRequired,
-    intl: React.PropTypes.object.isRequired
-  },
+class SensitiveButton extends React.PureComponent {
 
   render () {
     const { visible, active, onClick, intl } = this.props;
@@ -44,6 +38,13 @@ const SensitiveButton = React.createClass({
     );
   }
 
-});
+}
+
+SensitiveButton.propTypes = {
+  visible: PropTypes.bool,
+  active: PropTypes.bool,
+  onClick: PropTypes.func.isRequired,
+  intl: PropTypes.object.isRequired
+};
 
 export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton));
diff --git a/app/assets/javascripts/components/features/compose/index.jsx b/app/assets/javascripts/components/features/compose/index.jsx
index 33e16472c..eee5c440a 100644
--- a/app/assets/javascripts/components/features/compose/index.jsx
+++ b/app/assets/javascripts/components/features/compose/index.jsx
@@ -1,7 +1,7 @@
 import ComposeFormContainer from './containers/compose_form_container';
 import UploadFormContainer from './containers/upload_form_container';
 import NavigationContainer from './containers/navigation_container';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
+import PropTypes from 'prop-types';
 import { connect } from 'react-redux';
 import { mountCompose, unmountCompose } from '../../actions/compose';
 import { Link } from 'react-router';
@@ -22,24 +22,15 @@ const mapStateToProps = state => ({
   showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden'])
 });
 
-const Compose = React.createClass({
-
-  propTypes: {
-    dispatch: React.PropTypes.func.isRequired,
-    withHeader: React.PropTypes.bool,
-    showSearch: React.PropTypes.bool,
-    intl: React.PropTypes.object.isRequired
-  },
-
-  mixins: [PureRenderMixin],
+class Compose extends React.PureComponent {
 
   componentDidMount () {
     this.props.dispatch(mountCompose());
-  },
+  }
 
   componentWillUnmount () {
     this.props.dispatch(unmountCompose());
-  },
+  }
 
   render () {
     const { withHeader, showSearch, intl } = this.props;
@@ -82,6 +73,13 @@ const Compose = React.createClass({
     );
   }
 
-});
+}
+
+Compose.propTypes = {
+  dispatch: PropTypes.func.isRequired,
+  withHeader: PropTypes.bool,
+  showSearch: PropTypes.bool,
+  intl: PropTypes.object.isRequired
+};
 
 export default connect(mapStateToProps)(injectIntl(Compose));