diff --git a/services/web/frontend/js/features/chat/components/chat-pane.js b/services/web/frontend/js/features/chat/components/chat-pane.js
index a67670874f..224390492c 100644
--- a/services/web/frontend/js/features/chat/components/chat-pane.js
+++ b/services/web/frontend/js/features/chat/components/chat-pane.js
@@ -13,7 +13,7 @@ import withErrorBoundary from '../../../infrastructure/error-boundary'
import { FetchError } from '../../../infrastructure/fetch-json'
import { useChatContext } from '../context/chat-context'
-function ChatPane() {
+const ChatPane = React.memo(function ChatPane() {
const { t } = useTranslation()
const { chatIsOpen } = useLayoutContext({ chatIsOpen: PropTypes.bool })
@@ -81,7 +81,7 @@ function ChatPane() {
/>
)
-}
+})
function LoadingSpinner() {
const { t } = useTranslation()
diff --git a/services/web/frontend/js/features/chat/context/chat-context.js b/services/web/frontend/js/features/chat/context/chat-context.js
index f4bdcabf18..254c4054cd 100644
--- a/services/web/frontend/js/features/chat/context/chat-context.js
+++ b/services/web/frontend/js/features/chat/context/chat-context.js
@@ -264,19 +264,34 @@ export function ChatProvider({ children }) {
markMessagesAsRead,
])
- const value = {
- status: state.status,
- messages: state.messages,
- initialMessagesLoaded: state.initialMessagesLoaded,
- atEnd: state.atEnd,
- unreadMessageCount: state.unreadMessageCount,
- loadInitialMessages,
- loadMoreMessages,
- reset,
- sendMessage,
- markMessagesAsRead,
- error: state.error,
- }
+ const value = useMemo(
+ () => ({
+ status: state.status,
+ messages: state.messages,
+ initialMessagesLoaded: state.initialMessagesLoaded,
+ atEnd: state.atEnd,
+ unreadMessageCount: state.unreadMessageCount,
+ loadInitialMessages,
+ loadMoreMessages,
+ reset,
+ sendMessage,
+ markMessagesAsRead,
+ error: state.error,
+ }),
+ [
+ loadInitialMessages,
+ loadMoreMessages,
+ markMessagesAsRead,
+ reset,
+ sendMessage,
+ state.atEnd,
+ state.error,
+ state.initialMessagesLoaded,
+ state.messages,
+ state.status,
+ state.unreadMessageCount,
+ ]
+ )
return {children}
}
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js
index d1b468363c..eff7688a38 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js
+++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js
@@ -35,99 +35,104 @@ const chatContextPropTypes = {
unreadMessageCount: PropTypes.number.isRequired,
}
-function EditorNavigationToolbarRoot({
- onlineUsersArray,
- openDoc,
- openShareProjectModal,
-}) {
- const { user } = useApplicationContext(applicationContextPropTypes)
+const EditorNavigationToolbarRoot = React.memo(
+ function EditorNavigationToolbarRoot({
+ onlineUsersArray,
+ openDoc,
+ openShareProjectModal,
+ }) {
+ const { user } = useApplicationContext(applicationContextPropTypes)
- const {
- cobranding,
- loading,
- isRestrictedTokenMember,
- projectName,
- renameProject,
- isProjectOwner,
- } = useEditorContext(editorContextPropTypes)
+ const {
+ cobranding,
+ loading,
+ isRestrictedTokenMember,
+ projectName,
+ renameProject,
+ isProjectOwner,
+ } = useEditorContext(editorContextPropTypes)
- const {
- chatIsOpen,
- setChatIsOpen,
- reviewPanelOpen,
- setReviewPanelOpen,
- view,
- setView,
- setLeftMenuShown,
- pdfLayout,
- } = useLayoutContext(layoutContextPropTypes)
+ const {
+ chatIsOpen,
+ setChatIsOpen,
+ reviewPanelOpen,
+ setReviewPanelOpen,
+ view,
+ setView,
+ setLeftMenuShown,
+ pdfLayout,
+ } = useLayoutContext(layoutContextPropTypes)
- const { markMessagesAsRead, unreadMessageCount } = useChatContext(
- chatContextPropTypes
- )
+ const { markMessagesAsRead, unreadMessageCount } = useChatContext(
+ chatContextPropTypes
+ )
- const toggleChatOpen = useCallback(() => {
- if (!chatIsOpen) {
- markMessagesAsRead()
- }
- setChatIsOpen(value => !value)
- }, [chatIsOpen, setChatIsOpen, markMessagesAsRead])
+ const toggleChatOpen = useCallback(() => {
+ if (!chatIsOpen) {
+ markMessagesAsRead()
+ }
+ setChatIsOpen(value => !value)
+ }, [chatIsOpen, setChatIsOpen, markMessagesAsRead])
- const toggleReviewPanelOpen = useCallback(
- () => setReviewPanelOpen(value => !value),
- [setReviewPanelOpen]
- )
+ const toggleReviewPanelOpen = useCallback(
+ () => setReviewPanelOpen(value => !value),
+ [setReviewPanelOpen]
+ )
- const toggleHistoryOpen = useCallback(() => {
- setView(view === 'history' ? 'editor' : 'history')
- }, [view, setView])
+ const toggleHistoryOpen = useCallback(() => {
+ setView(view === 'history' ? 'editor' : 'history')
+ }, [view, setView])
- const togglePdfView = useCallback(() => {
- setView(view === 'pdf' ? 'editor' : 'pdf')
- }, [view, setView])
+ const togglePdfView = useCallback(() => {
+ setView(view === 'pdf' ? 'editor' : 'pdf')
+ }, [view, setView])
- const openShareModal = useCallback(() => {
- openShareProjectModal(isProjectOwner)
- }, [openShareProjectModal, isProjectOwner])
+ const openShareModal = useCallback(() => {
+ openShareProjectModal(isProjectOwner)
+ }, [openShareProjectModal, isProjectOwner])
- const onShowLeftMenuClick = useCallback(
- () => setLeftMenuShown(value => !value),
- [setLeftMenuShown]
- )
+ const onShowLeftMenuClick = useCallback(
+ () => setLeftMenuShown(value => !value),
+ [setLeftMenuShown]
+ )
- function goToUser(user) {
- if (user.doc && typeof user.row === 'number') {
- openDoc(user.doc, { gotoLine: user.row + 1 })
- }
+ const goToUser = useCallback(
+ user => {
+ if (user.doc && typeof user.row === 'number') {
+ openDoc(user.doc, { gotoLine: user.row + 1 })
+ }
+ },
+ [openDoc]
+ )
+
+ // using {display: 'none'} as 1:1 migration from Angular's ng-hide. Using
+ // `loading ? null : ` causes UI glitches
+ return (
+
+ )
}
-
- // using {display: 'none'} as 1:1 migration from Angular's ng-hide. Using
- // `loading ? null : ` causes UI glitches
- return (
-
- )
-}
+)
EditorNavigationToolbarRoot.propTypes = {
onlineUsersArray: PropTypes.array.isRequired,
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js
index cb0d0ee7fc..c152a2fd6b 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js
+++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js
@@ -15,7 +15,7 @@ import importOverleafModules from '../../../../macros/import-overleaf-module.mac
const [publishModalModules] = importOverleafModules('publishModal')
const PublishButton = publishModalModules?.import.default
-function ToolbarHeader({
+const ToolbarHeader = React.memo(function ToolbarHeader({
cobranding,
onShowLeftMenuClick,
chatIsOpen,
@@ -90,7 +90,7 @@ function ToolbarHeader({
)
-}
+})
ToolbarHeader.propTypes = {
onShowLeftMenuClick: PropTypes.func.isRequired,
diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-root.js b/services/web/frontend/js/features/file-tree/components/file-tree-root.js
index 15ceb50b67..ae9a114e00 100644
--- a/services/web/frontend/js/features/file-tree/components/file-tree-root.js
+++ b/services/web/frontend/js/features/file-tree/components/file-tree-root.js
@@ -18,7 +18,7 @@ import { useDroppable } from '../contexts/file-tree-draggable'
import { useFileTreeSocketListener } from '../hooks/file-tree-socket-listener'
import FileTreeModalCreateFile from './modals/file-tree-modal-create-file'
-function FileTreeRoot({
+const FileTreeRoot = React.memo(function FileTreeRoot({
projectId,
rootFolder,
rootDocId,
@@ -64,7 +64,7 @@ function FileTreeRoot({
)
-}
+})
function FileTreeRootFolder() {
useFileTreeSocketListener()
diff --git a/services/web/frontend/js/features/outline/components/outline-pane.js b/services/web/frontend/js/features/outline/components/outline-pane.js
index 813970cd88..6dbf1d6080 100644
--- a/services/web/frontend/js/features/outline/components/outline-pane.js
+++ b/services/web/frontend/js/features/outline/components/outline-pane.js
@@ -9,7 +9,7 @@ import localStorage from '../../../infrastructure/local-storage'
import withErrorBoundary from '../../../infrastructure/error-boundary'
import { useEditorContext } from '../../../shared/context/editor-context'
-function OutlinePane({
+const OutlinePane = React.memo(function OutlinePane({
isTexFile,
outline,
jumpToLine,
@@ -73,7 +73,7 @@ function OutlinePane({
) : null}
)
-}
+})
OutlinePane.propTypes = {
isTexFile: PropTypes.bool.isRequired,
diff --git a/services/web/frontend/js/features/preview/components/preview-pane.js b/services/web/frontend/js/features/preview/components/preview-pane.js
index e0ae33c54f..05183e8ccc 100644
--- a/services/web/frontend/js/features/preview/components/preview-pane.js
+++ b/services/web/frontend/js/features/preview/components/preview-pane.js
@@ -5,7 +5,7 @@ import PreviewLogsPane from './preview-logs-pane'
import PreviewFirstErrorPopUp from './preview-first-error-pop-up'
import { useTranslation } from 'react-i18next'
-function PreviewPane({
+const PreviewPane = React.memo(function PreviewPane({
compilerState,
onClearCache,
onRecompile,
@@ -139,7 +139,7 @@ function PreviewPane({
) : null}
>
)
-}
+})
PreviewPane.propTypes = {
compilerState: PropTypes.shape({
diff --git a/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js b/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js
index b46d5f0363..27e8b64192 100644
--- a/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js
+++ b/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js
@@ -79,7 +79,7 @@ export function useProjectContext() {
return context
}
-export default function ShareProjectModal({
+const ShareProjectModal = React.memo(function ShareProjectModal({
handleHide,
show,
animation = true,
@@ -161,10 +161,12 @@ export default function ShareProjectModal({
)
-}
+})
ShareProjectModal.propTypes = {
animation: PropTypes.bool,
handleHide: PropTypes.func.isRequired,
isAdmin: PropTypes.bool.isRequired,
show: PropTypes.bool.isRequired,
}
+
+export default ShareProjectModal
diff --git a/services/web/frontend/js/shared/context/application-context.js b/services/web/frontend/js/shared/context/application-context.js
index ca82192dbc..c627f8c684 100644
--- a/services/web/frontend/js/shared/context/application-context.js
+++ b/services/web/frontend/js/shared/context/application-context.js
@@ -1,4 +1,4 @@
-import React, { createContext, useContext } from 'react'
+import React, { createContext, useContext, useMemo } from 'react'
import PropTypes from 'prop-types'
export const ApplicationContext = createContext()
@@ -15,16 +15,20 @@ ApplicationContext.Provider.propTypes = {
}
export function ApplicationProvider({ children }) {
- const applicationContextValue = {
- gitBridgePublicBaseUrl: window.gitBridgePublicBaseUrl,
- }
+ const value = useMemo(() => {
+ const value = {
+ gitBridgePublicBaseUrl: window.gitBridgePublicBaseUrl,
+ }
- if (window.user.id) {
- applicationContextValue.user = window.user
- }
+ if (window.user.id) {
+ value.user = window.user
+ }
+
+ return value
+ }, [])
return (
-
+
{children}
)
diff --git a/services/web/frontend/js/shared/context/editor-context.js b/services/web/frontend/js/shared/context/editor-context.js
index 1c7a40a789..0ded53a256 100644
--- a/services/web/frontend/js/shared/context/editor-context.js
+++ b/services/web/frontend/js/shared/context/editor-context.js
@@ -1,4 +1,10 @@
-import React, { createContext, useCallback, useContext, useEffect } from 'react'
+import React, {
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+} from 'react'
import PropTypes from 'prop-types'
import useScopeValue from './util/scope-value-hook'
import useBrowserWindow from '../hooks/use-browser-window'
@@ -34,19 +40,23 @@ EditorContext.Provider.propTypes = {
export function EditorProvider({ children, settings }) {
const ide = useIdeContext()
- const cobranding = window.brandVariation
- ? {
- logoImgUrl: window.brandVariation.logo_url,
- brandVariationName: window.brandVariation.name,
- brandVariationId: window.brandVariation.id,
- brandId: window.brandVariation.brand_id,
- brandVariationHomeUrl: window.brandVariation.home_url,
- publishGuideHtml: window.brandVariation.publish_guide_html,
- partner: window.brandVariation.partner,
- brandedMenu: window.brandVariation.branded_menu,
- submitBtnHtml: window.brandVariation.submit_button_html,
- }
- : undefined
+ const cobranding = useMemo(
+ () =>
+ window.brandVariation
+ ? {
+ logoImgUrl: window.brandVariation.logo_url,
+ brandVariationName: window.brandVariation.name,
+ brandVariationId: window.brandVariation.id,
+ brandId: window.brandVariation.brand_id,
+ brandVariationHomeUrl: window.brandVariation.home_url,
+ publishGuideHtml: window.brandVariation.publish_guide_html,
+ partner: window.brandVariation.partner,
+ brandedMenu: window.brandVariation.branded_menu,
+ submitBtnHtml: window.brandVariation.submit_button_html,
+ }
+ : undefined,
+ []
+ )
const [loading] = useScopeValue('state.loading')
const [projectRootDocId] = useScopeValue('project.rootDoc_id')
@@ -87,23 +97,33 @@ export function EditorProvider({ children, settings }) {
)
}, [projectName, setTitle])
- const editorContextValue = {
- cobranding,
- hasPremiumCompile: compileGroup === 'priority',
- loading,
- projectId: window.project_id,
- projectRootDocId,
- projectName: projectName || '', // initially might be empty in Angular
- renameProject,
- isProjectOwner: ownerId === window.user.id,
- isRestrictedTokenMember: window.isRestrictedTokenMember,
- rootFolder,
- }
+ const value = useMemo(
+ () => ({
+ cobranding,
+ hasPremiumCompile: compileGroup === 'priority',
+ loading,
+ projectId: window.project_id,
+ projectRootDocId,
+ projectName: projectName || '', // initially might be empty in Angular
+ renameProject,
+ isProjectOwner: ownerId === window.user.id,
+ isRestrictedTokenMember: window.isRestrictedTokenMember,
+ rootFolder,
+ }),
+ [
+ cobranding,
+ compileGroup,
+ loading,
+ ownerId,
+ projectName,
+ projectRootDocId,
+ renameProject,
+ rootFolder,
+ ]
+ )
return (
-
- {children}
-
+ {children}
)
}
diff --git a/services/web/frontend/js/shared/context/layout-context.js b/services/web/frontend/js/shared/context/layout-context.js
index bac68a922f..46886fb3a8 100644
--- a/services/web/frontend/js/shared/context/layout-context.js
+++ b/services/web/frontend/js/shared/context/layout-context.js
@@ -1,4 +1,4 @@
-import React, { createContext, useContext, useCallback } from 'react'
+import React, { createContext, useContext, useCallback, useMemo } from 'react'
import PropTypes from 'prop-types'
import useScopeValue from './util/scope-value-hook'
import { useIdeContext } from './ide-context'
@@ -43,19 +43,32 @@ export function LayoutProvider({ children }) {
'ui.reviewPanelOpen'
)
const [leftMenuShown, setLeftMenuShown] = useScopeValue('ui.leftMenuShown')
- const [pdfLayout] = useScopeValue('ui.pdfLayout')
+ const [pdfLayout] = useScopeValue('ui.pdfLayout', $scope)
- const value = {
- view,
- setView,
- chatIsOpen,
- setChatIsOpen,
- reviewPanelOpen,
- setReviewPanelOpen,
- leftMenuShown,
- setLeftMenuShown,
- pdfLayout,
- }
+ const value = useMemo(
+ () => ({
+ view,
+ setView,
+ chatIsOpen,
+ setChatIsOpen,
+ reviewPanelOpen,
+ setReviewPanelOpen,
+ leftMenuShown,
+ setLeftMenuShown,
+ pdfLayout,
+ }),
+ [
+ chatIsOpen,
+ leftMenuShown,
+ pdfLayout,
+ reviewPanelOpen,
+ setChatIsOpen,
+ setLeftMenuShown,
+ setReviewPanelOpen,
+ setView,
+ view,
+ ]
+ )
return (
{children}