diff --git a/src/components/Feed/Feed.jsx b/src/components/Feed/Feed.jsx index 2a4b09e33..6d5f201fd 100644 --- a/src/components/Feed/Feed.jsx +++ b/src/components/Feed/Feed.jsx @@ -1,4 +1,5 @@ import React from 'react' +import _ from 'lodash' import cn from 'classnames' import moment from 'moment' import PropTypes from 'prop-types' @@ -82,6 +83,11 @@ class Feed extends React.Component { } } + shouldComponentUpdate(nextProps) { + // avoid re-rendering of this heavy component if no properties are changed + return !_.isEqual(this.props, nextProps) + } + render() { const { id, user, currentUser, topicMessage, totalComments, hasMoreComments, onLoadMoreComments, isLoadingComments, diff --git a/src/components/RichTextArea/RichTextArea.jsx b/src/components/RichTextArea/RichTextArea.jsx index 733e873d1..fe9f9354f 100644 --- a/src/components/RichTextArea/RichTextArea.jsx +++ b/src/components/RichTextArea/RichTextArea.jsx @@ -55,7 +55,15 @@ const blocks = [ class RichTextArea extends React.Component { constructor(props) { super(props) - this.state = {editorExpanded: false, editorState: EditorState.createEmpty(), titleValue: '', suggestions: [], allSuggestions:[], isPrivate: false} + this.state = { + editorExpanded: false, + editorState: EditorState.createEmpty(), + titleValue: '', + suggestions: [], + allSuggestions:[], + isPrivate: false + } + this.onTitleChange = this.onTitleChange.bind(this) this.onEditorChange = this.onEditorChange.bind(this) this.handleKeyCommand = this.handleKeyCommand.bind(this) @@ -139,14 +147,17 @@ class RichTextArea extends React.Component { do { if (currNode.className && currNode.className.indexOf - && currNode.className.indexOf('btn-close') > -1) { + && currNode.className.indexOf('btn-close') > -1 + ) { isCloseButton = true } if (currNode.className && currNode.className.indexOf - && currNode.className.indexOf('btn-close-creat') > -1) { - isCloseButton = true, + && currNode.className.indexOf('btn-close-creat') > -1 + ) { + isCloseButton = true + this.setState({ titleValue: '', editorState: EditorState.createEmpty(), @@ -170,7 +181,17 @@ class RichTextArea extends React.Component { if (!isEditor && !isCloseButton && hasContent) { return } - this.setState({editorExpanded: isEditor && !isCloseButton, isPrivate: isEditor && !isCloseButton ? this.state.isPrivate : false}) + + const editorExpanded = isEditor && !isCloseButton + const isPrivate = isEditor && !isCloseButton ? this.state.isPrivate : false + + // to avoid unnecessary re-rendering on every click, only update state if any of the values is updated + if (editorExpanded !== this.state.editorExpanded || isPrivate !== this.state.isPrivate) { + this.setState({ + editorExpanded, + isPrivate, + }) + } } handleKeyCommand(command) { diff --git a/src/projects/detail/containers/FeedContainer.js b/src/projects/detail/containers/FeedContainer.js index a2ad10de6..017dfcbdc 100644 --- a/src/projects/detail/containers/FeedContainer.js +++ b/src/projects/detail/containers/FeedContainer.js @@ -27,10 +27,10 @@ import PostsRefreshPrompt from '../components/PostsRefreshPrompt' import MediaQuery from 'react-responsive' import ChatButton from '../../../components/ChatButton/ChatButton' import NewPostMobile from '../../../components/Feed/NewPostMobile' -import ScrollableFeed from '../../../components/Feed/ScrollableFeed' import FullscreenFeedContainer from '../containers/FullscreenFeedContainer' import Section from '../components/Section' import SectionTitle from '../components/SectionTitle' +import SingleFeedContainer from './SingleFeedContainer' import { scrollToHash } from '../../../components/ScrollToAnchors' import { isSystemUser } from '../../../helpers/tcHelpers' @@ -58,6 +58,10 @@ class FeedView extends React.Component { this.onNewPostChange = this.onNewPostChange.bind(this) this.onEditMessage = this.onEditMessage.bind(this) this.onSaveMessageChange = this.onSaveMessageChange.bind(this) + this.onSaveMessage = this.onSaveMessage.bind(this) + this.onDeleteMessage = this.onDeleteMessage.bind(this) + this.onSaveTopic = this.onSaveTopic.bind(this) + this.onDeleteTopic = this.onDeleteTopic.bind(this) this.onEditTopic = this.onEditTopic.bind(this) this.onTopicChange = this.onTopicChange.bind(this) this.onRefreshFeeds = this.onRefreshFeeds.bind(this) @@ -403,25 +407,24 @@ class FeedView extends React.Component { this.enterFullscreen(feed.id) }} > - {feeds.map((feed) => (
-
diff --git a/src/projects/detail/containers/SingleFeedContainer.jsx b/src/projects/detail/containers/SingleFeedContainer.jsx new file mode 100644 index 000000000..844b9671c --- /dev/null +++ b/src/projects/detail/containers/SingleFeedContainer.jsx @@ -0,0 +1,63 @@ +/** + * The main purpose of this component is to bind methods which require feed.id + * so that we don't re-create them on every render like: + * onNewCommentChange: onNewCommentChange.bind(this, feed.id) + * So inside Feed component we can use shouldComponentUpdate to compare properties + * and only render if properties are changed. + * + * If we define methods like this + * onNewCommentChange: onNewCommentChange.bind(this, feed.id) + * then such function would be recreated on every render and shouldComponentUpdate + * will return true for every render. + */ +import React from 'react' +import _ from 'lodash' + +import ScrollableFeed from '../../../components/Feed/ScrollableFeed' + +const bindMethods = [ + 'onNewCommentChange', + 'onAddNewComment', + 'onLoadMoreComments', + 'onEditMessage', + 'onSaveMessageChange', + 'onSaveMessage', + 'onDeleteMessage', + 'onEditTopic', + 'onTopicChange', + 'onSaveTopic', + 'onDeleteTopic', + 'onEnterFullscreenClick', +] + +class SingleFeedContainer extends React.Component { + constructor(props) { + super(props) + + bindMethods.forEach((method) => { + // We cannot use an arrow function here, as later other component can apply .bind to these methods + this[method] = function() { + // we cannot use .bind here as we already bound "this" in these methods to another React component and we don't want to override it + return this.props[method](...[this.props.id, ...arguments]) + }.bind(this) + }) + } + + render() { + const nonBindProps = _.omit(this.props, bindMethods) + const bindProps = _.zipObject(bindMethods, bindMethods.map((method) => this[method])) + + return ( + + ) + } +} + +export default SingleFeedContainer \ No newline at end of file