From a01ba43ac8631d2928f1567a462c402e1f8930db Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 29 Apr 2019 17:54:08 +0700 Subject: [PATCH 1/3] fix topics re-rendering on every symbol typing --- src/components/Feed/Feed.jsx | 5 ++ .../detail/containers/FeedContainer.js | 55 ++++++++--------- .../detail/containers/SingleFeedContainer.jsx | 59 +++++++++++++++++++ 3 files changed, 90 insertions(+), 29 deletions(-) create mode 100644 src/projects/detail/containers/SingleFeedContainer.jsx diff --git a/src/components/Feed/Feed.jsx b/src/components/Feed/Feed.jsx index 2a4b09e33..000fa8abc 100644 --- a/src/components/Feed/Feed.jsx +++ b/src/components/Feed/Feed.jsx @@ -82,6 +82,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/projects/detail/containers/FeedContainer.js b/src/projects/detail/containers/FeedContainer.js index a2ad10de6..d06962793 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' @@ -403,25 +403,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..3835c9907 --- /dev/null +++ b/src/projects/detail/containers/SingleFeedContainer.jsx @@ -0,0 +1,59 @@ +/** + * 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) => { + this[method] = () => this.props[method](this.props.id) + }) + } + + 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 From ed4742e30e1e0fb75bdda4c87d91d50c35228b00 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 29 Apr 2019 17:55:31 +0700 Subject: [PATCH 2/3] fix topics re-rendering on every click also some formatting for easier reading --- src/components/RichTextArea/RichTextArea.jsx | 31 ++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) 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) { From dee37d4750f5731b666dd2a59316ca55823aa370 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Wed, 1 May 2019 17:57:05 +0700 Subject: [PATCH 3/3] fix some actions with posts after fixing re-rendering --- src/components/Feed/Feed.jsx | 1 + src/projects/detail/containers/FeedContainer.js | 4 ++++ src/projects/detail/containers/SingleFeedContainer.jsx | 6 +++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/Feed/Feed.jsx b/src/components/Feed/Feed.jsx index 000fa8abc..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' diff --git a/src/projects/detail/containers/FeedContainer.js b/src/projects/detail/containers/FeedContainer.js index d06962793..017dfcbdc 100644 --- a/src/projects/detail/containers/FeedContainer.js +++ b/src/projects/detail/containers/FeedContainer.js @@ -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) diff --git a/src/projects/detail/containers/SingleFeedContainer.jsx b/src/projects/detail/containers/SingleFeedContainer.jsx index 3835c9907..844b9671c 100644 --- a/src/projects/detail/containers/SingleFeedContainer.jsx +++ b/src/projects/detail/containers/SingleFeedContainer.jsx @@ -35,7 +35,11 @@ class SingleFeedContainer extends React.Component { super(props) bindMethods.forEach((method) => { - this[method] = () => this.props[method](this.props.id) + // 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) }) }