From 43b44ecc6aede8d1221a65a8df79259dc4602686 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Francisco=20Siebel?= <jfsiebel@gmail.com>
Date: Fri, 23 Nov 2018 12:14:48 -0200
Subject: [PATCH] Improve user-list-content performance. close #6268

---
 .../imports/ui/components/modal/service.js    |   4 +-
 .../ui/components/nav-bar/component.jsx       |   4 +-
 .../nav-bar/settings-dropdown/component.jsx   |  10 +-
 .../nav-bar/settings-dropdown/container.jsx   |   6 +-
 .../ui/components/user-list/component.jsx     |  18 ++--
 .../ui/components/user-list/container.jsx     |   7 +-
 .../ui/components/user-list/service.js        |   3 +
 .../breakout-room/component.jsx               |   3 +-
 .../user-list/user-list-content/component.jsx |  23 ++--
 .../user-list/user-list-content/container.jsx |   4 +-
 .../user-messages/component.jsx               |   4 +-
 .../user-participants/component.jsx           | 102 ++++++++++--------
 .../user-participants/container.jsx           |  11 ++
 .../user-list-item/component.jsx              |  20 ++--
 .../user-list-item/container.jsx              |  11 ++
 .../user-dropdown/component.jsx               |   9 +-
 .../user-list-item/user-name/component.jsx    |  16 ++-
 .../user-options/component.jsx                |   4 +-
 .../user-options/container.jsx                |   4 +-
 .../user-polls/component.jsx                  |   8 +-
 bigbluebutton-html5/package-lock.json         |   2 +-
 21 files changed, 144 insertions(+), 129 deletions(-)
 create mode 100644 bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/container.jsx
 create mode 100644 bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx

diff --git a/bigbluebutton-html5/imports/ui/components/modal/service.js b/bigbluebutton-html5/imports/ui/components/modal/service.js
index d920ec8390..f842730245 100644
--- a/bigbluebutton-html5/imports/ui/components/modal/service.js
+++ b/bigbluebutton-html5/imports/ui/components/modal/service.js
@@ -1,5 +1,5 @@
 import { Tracker } from 'meteor/tracker';
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
 
 const currentModal = {
   component: null,
@@ -19,7 +19,7 @@ export const getModal = () => {
 };
 
 export const withModalMounter = ComponentToWrap =>
-  class ModalMounterWrapper extends Component {
+  class ModalMounterWrapper extends PureComponent {
     static mount(modalComponent) {
       showModal(null);
       // defer the execution to a subsequent event loop
diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
index fb8ae22b3a..d4a16804c4 100755
--- a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
 import PropTypes from 'prop-types';
 import _ from 'lodash';
 import cx from 'classnames';
@@ -67,7 +67,7 @@ const openBreakoutJoinConfirmation = (breakout, breakoutName, mountModal) =>
 const closeBreakoutJoinConfirmation = mountModal =>
   mountModal(null);
 
-class NavBar extends Component {
+class NavBar extends PureComponent {
   constructor(props) {
     super(props);
 
diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx
index 108b08ce51..5b263c094f 100755
--- a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
 import { defineMessages, injectIntl } from 'react-intl';
 import cx from 'classnames';
 import _ from 'lodash';
@@ -81,7 +81,7 @@ const intlMessages = defineMessages({
   },
 });
 
-class SettingsDropdown extends Component {
+class SettingsDropdown extends PureComponent {
   constructor(props) {
     super(props);
 
@@ -98,7 +98,7 @@ class SettingsDropdown extends Component {
     const { fullscreenLabel, fullscreenDesc, fullscreenIcon } = this.checkFullscreen(this.props);
     const { showHelpButton: helpButton } = Meteor.settings.public.app;
 
-    this.menuItems =_.compact( [(<DropdownListItem
+    this.menuItems = _.compact([(<DropdownListItem
       key={_.uniqueId('list-item-')}
       icon={fullscreenIcon}
       label={fullscreenLabel}
@@ -142,14 +142,12 @@ class SettingsDropdown extends Component {
         description={intl.formatMessage(intlMessages.leaveSessionDesc)}
         onClick={() => mountModal(<LogoutConfirmationContainer />)}
       />),
-    ])
+    ]);
 
     // Removes fullscreen button if not on Android
     if (!isAndroid) {
       this.menuItems.shift();
     }
-
-
   }
 
   componentWillReceiveProps(nextProps) {
diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/container.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/container.jsx
index 6a13b39a6e..9036ae76a5 100755
--- a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/container.jsx
@@ -1,9 +1,9 @@
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
 import browser from 'browser-detect';
 import SettingsDropdown from './component';
 import { toggleFullScreen } from './service';
 
-export default class SettingsDropdownContainer extends Component {
+export default class SettingsDropdownContainer extends PureComponent {
   constructor(props) {
     super(props);
 
@@ -51,7 +51,7 @@ export default class SettingsDropdownContainer extends Component {
 
   render() {
     const handleToggleFullscreen = toggleFullScreen;
-    const isFullScreen = this.state.isFullScreen;
+    const { isFullScreen } = this.state;
     const result = browser();
     const isAndroid = (result && result.os) ? result.os.includes('Android') : false;
 
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
index 3643ae3170..8b6c91d395 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
 import { injectIntl } from 'react-intl';
 import PropTypes from 'prop-types';
 import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
@@ -8,13 +8,14 @@ import UserContentContainer from './user-list-content/container';
 
 const propTypes = {
   openChats: PropTypes.arrayOf(String).isRequired,
-  users: PropTypes.arrayOf(Object).isRequired,
   compact: PropTypes.bool,
   intl: PropTypes.shape({
     formatMessage: PropTypes.func.isRequired,
   }).isRequired,
   currentUser: PropTypes.shape({}).isRequired,
-  meeting: PropTypes.shape({}),
+  CustomLogoUrl: PropTypes.string.isRequired,
+  handleEmojiChange: PropTypes.func.isRequired,
+  getUsersId: PropTypes.func.isRequired,
   isBreakoutRoom: PropTypes.bool,
   getAvailableActions: PropTypes.func.isRequired,
   normalizeEmojiName: PropTypes.func.isRequired,
@@ -35,17 +36,13 @@ const propTypes = {
 const defaultProps = {
   compact: false,
   isBreakoutRoom: false,
-  // This one is kinda tricky, meteor takes sometime to fetch the data and passing down
-  // So the first time its create, the meeting comes as null, sending an error to the client.
-  meeting: {},
 };
 
-class UserList extends Component {
+class UserList extends PureComponent {
   render() {
     const {
       intl,
       openChats,
-      users,
       compact,
       currentUser,
       isBreakoutRoom,
@@ -56,7 +53,6 @@ class UserList extends Component {
       muteAllUsers,
       muteAllExceptPresenter,
       changeRole,
-      meeting,
       getAvailableActions,
       normalizeEmojiName,
       isMeetingLocked,
@@ -69,6 +65,7 @@ class UserList extends Component {
       getEmoji,
       showBranding,
       hasBreakoutRoom,
+      getUsersId,
     } = this.props;
 
     return (
@@ -83,7 +80,6 @@ class UserList extends Component {
           {...{
           intl,
           openChats,
-          users,
           compact,
           currentUser,
           isBreakoutRoom,
@@ -94,7 +90,6 @@ class UserList extends Component {
           muteAllUsers,
           muteAllExceptPresenter,
           changeRole,
-          meeting,
           getAvailableActions,
           normalizeEmojiName,
           isMeetingLocked,
@@ -105,6 +100,7 @@ class UserList extends Component {
           getEmojiList,
           getEmoji,
           hasBreakoutRoom,
+          getUsersId,
         }
       }
         />}
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx
index c6d4b916d8..8599e4c68d 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx
@@ -2,16 +2,14 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { withTracker } from 'meteor/react-meteor-data';
 import { meetingIsBreakout } from '/imports/ui/components/app/service';
-import Meetings from '/imports/api/meetings';
 import getFromUserSettings from '/imports/ui/services/users-settings';
 import Service from './service';
 import UserList from './component';
 
 const propTypes = {
   openChats: PropTypes.arrayOf(String).isRequired,
-  users: PropTypes.arrayOf(Object).isRequired,
   currentUser: PropTypes.shape({}).isRequired,
-  meeting: PropTypes.shape({}).isRequired,
+  getUsersId: PropTypes.func.isRequired,
   isBreakoutRoom: PropTypes.bool.isRequired,
   getAvailableActions: PropTypes.func.isRequired,
   normalizeEmojiName: PropTypes.func.isRequired,
@@ -33,9 +31,8 @@ const UserListContainer = props => <UserList {...props} />;
 UserListContainer.propTypes = propTypes;
 
 export default withTracker(({ chatID, compact }) => ({
-  users: Service.getUsers(),
-  meeting: Meetings.findOne({}),
   hasBreakoutRoom: Service.hasBreakoutRoom(),
+  getUsersId: Service.getUsersId,
   currentUser: Service.getCurrentUser(),
   openChats: Service.getOpenChats(chatID),
   isBreakoutRoom: meetingIsBreakout(),
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js
index 3678f7d61d..dbae3223c6 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/service.js
+++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js
@@ -188,6 +188,8 @@ const getUsers = () => {
     .sort(sortUsers);
 };
 
+const getUsersId = () => getUsers().map(u => u.id);
+
 const hasBreakoutRoom = () => Breakouts.find({ parentMeetingId: Auth.meetingID }).count() > 0;
 
 const getOpenChats = (chatID) => {
@@ -443,6 +445,7 @@ export default {
   muteAllExceptPresenter,
   changeRole,
   getUsers,
+  getUsersId,
   getOpenChats,
   getCurrentUser,
   getAvailableActions,
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/breakout-room/component.jsx
index b0df572d51..5638e6ce9d 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/breakout-room/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/breakout-room/component.jsx
@@ -1,5 +1,4 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
+import React from 'react';
 import { defineMessages, injectIntl } from 'react-intl';
 import { Session } from 'meteor/session';
 import Icon from '/imports/ui/components/icon/component';
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx
index 9932dee6e9..17188a23ad 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx
@@ -1,20 +1,18 @@
-import React from 'react';
+import React, { PureComponent } from 'react';
 import PropTypes from 'prop-types';
 import { styles } from './styles';
-import UserParticipants from './user-participants/component';
+import UserParticipantsContainer from './user-participants/container';
 import UserMessages from './user-messages/component';
 import UserPolls from './user-polls/component';
 import BreakoutRoomItem from './breakout-room/component';
 
 const propTypes = {
   openChats: PropTypes.arrayOf(String).isRequired,
-  users: PropTypes.arrayOf(Object).isRequired,
   compact: PropTypes.bool,
   intl: PropTypes.shape({
     formatMessage: PropTypes.func.isRequired,
   }).isRequired,
   currentUser: PropTypes.shape({}).isRequired,
-  meeting: PropTypes.shape({}),
   isBreakoutRoom: PropTypes.bool,
   getAvailableActions: PropTypes.func.isRequired,
   normalizeEmojiName: PropTypes.func.isRequired,
@@ -29,24 +27,23 @@ const propTypes = {
   changeRole: PropTypes.func.isRequired,
   roving: PropTypes.func.isRequired,
   getGroupChatPrivate: PropTypes.func.isRequired,
+  handleEmojiChange: PropTypes.func.isRequired,
+  getUsersId: PropTypes.func.isRequired,
+  pollIsOpen: PropTypes.bool.isRequired,
+  forcePollOpen: PropTypes.bool.isRequired,
 };
 
 const defaultProps = {
   compact: false,
   isBreakoutRoom: false,
-  // This one is kinda tricky, meteor takes sometime to fetch the data and passing down
-  // So the first time its create, the meeting comes as null, sending an error to the client.
-  meeting: {},
 };
 
-class UserContent extends React.Component {
+class UserContent extends PureComponent {
   render() {
     const {
-      users,
       compact,
       intl,
       currentUser,
-      meeting,
       isBreakoutRoom,
       setEmojiStatus,
       assignPresenter,
@@ -68,6 +65,7 @@ class UserContent extends React.Component {
       pollIsOpen,
       forcePollOpen,
       hasBreakoutRoom,
+      getUsersId,
     } = this.props;
 
     return (
@@ -93,13 +91,11 @@ class UserContent extends React.Component {
           }}
         />
         <BreakoutRoomItem isPresenter={currentUser.isPresenter} hasBreakoutRoom={hasBreakoutRoom} />
-        <UserParticipants
+        <UserParticipantsContainer
           {...{
-            users,
             compact,
             intl,
             currentUser,
-            meeting,
             isBreakoutRoom,
             setEmojiStatus,
             assignPresenter,
@@ -116,6 +112,7 @@ class UserContent extends React.Component {
             getEmojiList,
             getEmoji,
             getGroupChatPrivate,
+            getUsersId,
           }}
         />
       </div>
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx
index f9daa37e1f..2e85bee545 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx
@@ -3,9 +3,9 @@ import { withTracker } from 'meteor/react-meteor-data';
 import { Session } from 'meteor/session';
 import UserContent from './component';
 
-const UserContentContainer = ({ ...props }) => <UserContent {...props} />;
+const UserContentContainer = props => <UserContent {...props} />;
 
-export default withTracker(({ }) => ({
+export default withTracker(() => ({
   pollIsOpen: Session.equals('isPollOpen', true),
   forcePollOpen: Session.equals('forcePollOpen', true),
 }))(UserContentContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx
index ca2b28c5a4..cfb12a669f 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
 import { TransitionGroup, CSSTransition } from 'react-transition-group';
 import PropTypes from 'prop-types';
 import { defineMessages } from 'react-intl';
@@ -38,7 +38,7 @@ const intlMessages = defineMessages({
   },
 });
 
-class UserMessages extends Component {
+class UserMessages extends PureComponent {
   constructor() {
     super();
 
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx
index b3907e9329..ad4c2ab6cf 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx
@@ -4,17 +4,21 @@ import { defineMessages } from 'react-intl';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
 import { styles } from '/imports/ui/components/user-list/user-list-content/styles';
-import UserListItem from './user-list-item/component';
+import _ from 'lodash';
+import UserListItemContainer from './user-list-item/container';
 import UserOptionsContainer from './user-options/container';
 
 const propTypes = {
-  users: PropTypes.arrayOf(Object).isRequired,
   compact: PropTypes.bool,
   intl: PropTypes.shape({
     formatMessage: PropTypes.func.isRequired,
   }).isRequired,
   currentUser: PropTypes.shape({}).isRequired,
-  meeting: PropTypes.shape({}),
+  meeting: PropTypes.shape({}).isRequired,
+  users: PropTypes.arrayOf(PropTypes.string).isRequired,
+  getGroupChatPrivate: PropTypes.func.isRequired,
+  handleEmojiChange: PropTypes.func.isRequired,
+  getUsersId: PropTypes.func.isRequired,
   isBreakoutRoom: PropTypes.bool,
   setEmojiStatus: PropTypes.func.isRequired,
   assignPresenter: PropTypes.func.isRequired,
@@ -32,9 +36,6 @@ const propTypes = {
 const defaultProps = {
   compact: false,
   isBreakoutRoom: false,
-  // This one is kinda tricky, meteor takes sometime to fetch the data and passing down
-  // So the first time its create, the meeting comes as null, sending an error to the client.
-  meeting: {},
 };
 
 const listTransition = {
@@ -83,6 +84,12 @@ class UserParticipants extends Component {
     }
   }
 
+  shouldComponentUpdate(nextProps, nextState) {
+    const isPropsEqual = _.isEqual(this.props, nextProps);
+    const isStateEqual = _.isEqual(this.state, nextState);
+    return !isPropsEqual || !isStateEqual;
+  }
+
   componentDidUpdate(prevProps, prevState) {
     if (this.state.index === -1) {
       return;
@@ -106,57 +113,60 @@ class UserParticipants extends Component {
       getAvailableActions,
       normalizeEmojiName,
       isMeetingLocked,
-      users,
       changeRole,
       assignPresenter,
       setEmojiStatus,
       removeUser,
       toggleVoice,
-      getGroupChatPrivate, // // TODO check if this is used
-      handleEmojiChange, // // TODO add to props validation
+      getGroupChatPrivate,
+      handleEmojiChange,
       getEmojiList,
       getEmoji,
+      users,
     } = this.props;
 
     let index = -1;
 
-    return users.map(user => (
-      <CSSTransition
-        classNames={listTransition}
-        appear
-        enter
-        exit
-        timeout={0}
-        component="div"
-        className={cx(styles.participantsList)}
-        key={user.id}
-      >
-        <div ref={(node) => { this.userRefs[index += 1] = node; }}>
-          <UserListItem
-            {...{
-              user,
-              currentUser,
-              compact,
-              isBreakoutRoom,
-              meeting,
-              getAvailableActions,
-              normalizeEmojiName,
-              isMeetingLocked,
-              handleEmojiChange,
-              getEmojiList,
-              getEmoji,
-              setEmojiStatus,
-              assignPresenter,
-              removeUser,
-              toggleVoice,
-              changeRole,
-              getGroupChatPrivate,
-            }}
-            getScrollContainerRef={this.getScrollContainerRef}
-          />
-        </div>
-      </CSSTransition>
-    ));
+    const { meetingId } = meeting;
+
+    return users.map(u =>
+      (
+        <CSSTransition
+          classNames={listTransition}
+          appear
+          enter
+          exit
+          timeout={0}
+          component="div"
+          className={cx(styles.participantsList)}
+          key={u}
+        >
+          <div ref={(node) => { this.userRefs[index += 1] = node; }}>
+            <UserListItemContainer
+              {...{
+                currentUser,
+                compact,
+                isBreakoutRoom,
+                meetingId,
+                getAvailableActions,
+                normalizeEmojiName,
+                isMeetingLocked,
+                handleEmojiChange,
+                getEmojiList,
+                getEmoji,
+                setEmojiStatus,
+                assignPresenter,
+                removeUser,
+                toggleVoice,
+                changeRole,
+                getGroupChatPrivate,
+              }}
+              userId={u}
+              getScrollContainerRef={this.getScrollContainerRef}
+            />
+          </div>
+        </CSSTransition>
+      ));
   }
 
   focusUserItem(index) {
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/container.jsx
new file mode 100644
index 0000000000..b5ef848a0d
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/container.jsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import { withTracker } from 'meteor/react-meteor-data';
+import Meetings from '/imports/api/meetings';
+import UserParticipants from './component';
+
+const UserParticipantsContainer = ({ ...props }) => <UserParticipants {...props} />;
+
+export default withTracker(({ getUsersId }) => ({
+  meeting: Meetings.findOne({}),
+  users: getUsersId(),
+}))(UserParticipantsContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx
index 54d5fa0d7b..1bafe9249b 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx
@@ -1,17 +1,10 @@
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
 import PropTypes from 'prop-types';
 import { injectIntl } from 'react-intl';
+import _ from 'lodash';
 import UserDropdown from './user-dropdown/component';
 
 const propTypes = {
-  user: PropTypes.shape({
-    name: PropTypes.string.isRequired,
-    isPresenter: PropTypes.bool.isRequired,
-    isVoiceUser: PropTypes.bool.isRequired,
-    isModerator: PropTypes.bool.isRequired,
-    image: PropTypes.string,
-  }).isRequired,
-
   currentUser: PropTypes.shape({
     id: PropTypes.string.isRequired,
   }).isRequired,
@@ -22,7 +15,6 @@ const propTypes = {
   }).isRequired,
   isBreakoutRoom: PropTypes.bool,
   getAvailableActions: PropTypes.func.isRequired,
-  meeting: PropTypes.shape({}).isRequired,
   isMeetingLocked: PropTypes.func.isRequired,
   normalizeEmojiName: PropTypes.func.isRequired,
   getScrollContainerRef: PropTypes.func.isRequired,
@@ -32,9 +24,10 @@ const defaultProps = {
   isBreakoutRoom: false,
 };
 
-class UserListItem extends Component {
+class UserListItem extends PureComponent {
   render() {
     const {
+      user,
       assignPresenter,
       compact,
       currentUser,
@@ -48,12 +41,11 @@ class UserListItem extends Component {
       intl,
       isBreakoutRoom,
       isMeetingLocked,
-      meeting,
+      meetingId,
       normalizeEmojiName,
       removeUser,
       setEmojiStatus,
       toggleVoice,
-      user,
     } = this.props;
 
     const contents = (<UserDropdown
@@ -71,7 +63,7 @@ class UserListItem extends Component {
         intl,
         isBreakoutRoom,
         isMeetingLocked,
-        meeting,
+        meetingId,
         normalizeEmojiName,
         removeUser,
         setEmojiStatus,
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx
new file mode 100644
index 0000000000..e27d54154b
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import { withTracker } from 'meteor/react-meteor-data';
+import Users from '/imports/api/users';
+import mapUser from '/imports/ui/services/user/mapUser';
+import UserListItem from './component';
+
+const UserListItemContainer = props => <UserListItem {...props} />;
+
+export default withTracker(({ userId }) => ({
+  user: mapUser(Users.findOne({ userId })),
+}))(UserListItemContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx
index ccb9aa475b..8285826f0f 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
 import { defineMessages } from 'react-intl';
 import PropTypes from 'prop-types';
 import { findDOMNode } from 'react-dom';
@@ -90,12 +90,11 @@ const propTypes = {
     formatMessage: PropTypes.func.isRequired,
   }).isRequired,
   normalizeEmojiName: PropTypes.func.isRequired,
-  meeting: PropTypes.shape({}).isRequired,
   isMeetingLocked: PropTypes.func.isRequired,
   getScrollContainerRef: PropTypes.func.isRequired,
 };
 
-class UserDropdown extends Component {
+class UserDropdown extends PureComponent {
   /**
    * Return true if the content fit on the screen, false otherwise.
    *
@@ -436,7 +435,7 @@ class UserDropdown extends Component {
       user,
       intl,
       isMeetingLocked,
-      meeting,
+      meetingId,
     } = this.props;
 
     const {
@@ -484,7 +483,7 @@ class UserDropdown extends Component {
               user,
               compact,
               intl,
-              meeting,
+              meetingId,
               isMeetingLocked,
               userAriaLabel,
               isActionsOpen,
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-name/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-name/component.jsx
index 231171c78e..d3c7e26181 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-name/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-name/component.jsx
@@ -43,9 +43,10 @@ const propTypes = {
   intl: PropTypes.shape({
     formatMessage: PropTypes.func.isRequired,
   }).isRequired,
-  meeting: PropTypes.shape({}).isRequired,
   isMeetingLocked: PropTypes.func.isRequired,
   userAriaLabel: PropTypes.string.isRequired,
+  meetingId: PropTypes.string.isRequired,
+  isActionsOpen: PropTypes.bool.isRequired,
 };
 
 const UserName = (props) => {
@@ -54,7 +55,7 @@ const UserName = (props) => {
     intl,
     compact,
     isMeetingLocked,
-    meeting,
+    meetingId,
     userAriaLabel,
     isActionsOpen,
   } = props;
@@ -69,7 +70,7 @@ const UserName = (props) => {
     return null;
   }
 
-  if (isMeetingLocked(meeting.meetingId) && user.isLocked) {
+  if (isMeetingLocked(meetingId) && user.isLocked) {
     userNameSub.push(<span>
       <Icon iconName="lock" />
       {intl.formatMessage(messages.locked)}
@@ -81,7 +82,12 @@ const UserName = (props) => {
   }
 
   return (
-    <div className={styles.userName} role="button" aria-label={userAriaLabel} aria-expanded={isActionsOpen}>
+    <div
+      className={styles.userName}
+      role="button"
+      aria-label={userAriaLabel}
+      aria-expanded={isActionsOpen}
+    >
       <span className={styles.userNameMain}>
         {user.name} <i>{(user.isCurrent) ? `(${intl.formatMessage(messages.you)})` : ''}</i>
       </span>
@@ -90,7 +96,7 @@ const UserName = (props) => {
           <span className={styles.userNameSub}>
             {userNameSub.reduce((prev, curr) => [prev, ' | ', curr])}
           </span>
-        : null
+          : null
       }
     </div>
   );
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx
index 3e689940ef..2f4e7eb515 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl } from 'react-intl';
 import _ from 'lodash';
@@ -69,7 +69,7 @@ const intlMessages = defineMessages({
   },
 });
 
-class UserOptions extends Component {
+class UserOptions extends PureComponent {
   constructor(props) {
     super(props);
 
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx
index 59561dab0d..03872beb03 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
 import PropTypes from 'prop-types';
 import logger from '/imports/startup/client/logger';
 import Auth from '/imports/ui/services/auth';
@@ -15,7 +15,7 @@ const propTypes = {
   setEmojiStatus: PropTypes.func.isRequired,
 };
 
-export default class UserOptionsContainer extends Component {
+export default class UserOptionsContainer extends PureComponent {
   constructor(props) {
     super(props);
     const meeting = Meetings.findOne({ meetingId: Auth.meetingID });
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/component.jsx
index 1d9098daea..f0776f8253 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/component.jsx
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
 import _ from 'lodash';
 import { defineMessages, injectIntl } from 'react-intl';
 import Icon from '/imports/ui/components/icon/component';
@@ -12,11 +12,7 @@ const intlMessages = defineMessages({
   },
 });
 
-class UserPolls extends Component {
-  constructor(props) {
-    super(props);
-  }
-
+class UserPolls extends PureComponent {
   render() {
     const {
       intl,
diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json
index 7b6939da5f..4d75af4431 100755
--- a/bigbluebutton-html5/package-lock.json
+++ b/bigbluebutton-html5/package-lock.json
@@ -6085,4 +6085,4 @@
       }
     }
   }
-}
+}
\ No newline at end of file
-- 
GitLab