From 13e6166259f08016d5c225fc543b04595b83f7bb Mon Sep 17 00:00:00 2001
From: Alf Eaton <75253002+aeaton-overleaf@users.noreply.github.com>
Date: Tue, 4 May 2021 12:36:22 +0100
Subject: [PATCH] Merge pull request #3974 from
overleaf/ae-refactor-context-hooks-usememo
Refactor functions from hooks into context providers
GitOrigin-RevId: f985ec15c16bdb49bedf7b64a0f5fe2853b6bb85
---
.../contexts/file-tree-actionable.js | 112 ++++++--------
.../file-tree/contexts/file-tree-mutable.js | 138 ++++++++----------
.../contexts/file-tree-selectable.js | 86 ++++++-----
3 files changed, 155 insertions(+), 181 deletions(-)
diff --git a/services/web/frontend/js/features/file-tree/contexts/file-tree-actionable.js b/services/web/frontend/js/features/file-tree/contexts/file-tree-actionable.js
index 99b915df10..e662f0121f 100644
--- a/services/web/frontend/js/features/file-tree/contexts/file-tree-actionable.js
+++ b/services/web/frontend/js/features/file-tree/contexts/file-tree-actionable.js
@@ -124,41 +124,13 @@ export function FileTreeActionableProvider({ hasWritePermissions, children }) {
defaultState
)
- return (
-
- {children}
-
- )
-}
-
-FileTreeActionableProvider.propTypes = {
- hasWritePermissions: PropTypes.bool.isRequired,
- children: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.node),
- PropTypes.node,
- ]).isRequired,
-}
-
-export function useFileTreeActionable() {
- const {
- isDeleting,
- isRenaming,
- isMoving,
- isCreatingFile,
- isCreatingFolder,
- inFlight,
- error,
- actionedEntities,
- newFileCreateMode,
- dispatch,
- } = useContext(FileTreeActionableContext)
const { projectId } = useFileTreeMainContext()
const { fileTreeData, dispatchRename, dispatchMove } = useFileTreeMutable()
const { selectedEntityIds } = useFileTreeSelectable()
const startRenaming = useCallback(() => {
dispatch({ type: ACTION_TYPES.START_RENAME })
- }, [dispatch])
+ }, [])
// update the entity with the new name immediately in the tree, but revert to
// the old name if the sync fails
@@ -185,7 +157,7 @@ export function useFileTreeActionable() {
}
)
},
- [dispatch, dispatchRename, fileTreeData, projectId, selectedEntityIds]
+ [dispatchRename, fileTreeData, projectId, selectedEntityIds]
)
const isDuplicate = useCallback(
@@ -203,9 +175,9 @@ export function useFileTreeActionable() {
entityId => findInTreeOrThrow(fileTreeData, entityId).entity
)
dispatch({ type: ACTION_TYPES.START_DELETE, actionedEntities })
- }, [dispatch, fileTreeData, selectedEntityIds])
+ }, [fileTreeData, selectedEntityIds])
- // deletes entities in serie. Tree will be updated via the socket event
+ // deletes entities in series. Tree will be updated via the socket event
const finishDeleting = useCallback(() => {
dispatch({ type: ACTION_TYPES.DELETING })
@@ -227,7 +199,7 @@ export function useFileTreeActionable() {
// set an error and allow user to retry
dispatch({ type: ACTION_TYPES.ERROR, error })
})
- }, [dispatch, fileTreeData, projectId, selectedEntityIds])
+ }, [fileTreeData, projectId, selectedEntityIds])
// moves entities. Tree is updated immediately and data are sync'd after.
const finishMoving = useCallback(
@@ -266,20 +238,20 @@ export function useFileTreeActionable() {
dispatch({ type: ACTION_TYPES.ERROR, error })
})
},
- [dispatch, dispatchMove, fileTreeData, projectId]
+ [dispatchMove, fileTreeData, projectId]
)
const startCreatingFolder = useCallback(() => {
dispatch({ type: ACTION_TYPES.START_CREATE_FOLDER })
- }, [dispatch])
+ }, [])
+
+ const parentFolderId = useMemo(
+ () => getSelectedParentFolderId(fileTreeData, selectedEntityIds),
+ [fileTreeData, selectedEntityIds]
+ )
const finishCreatingEntity = useCallback(
entity => {
- const parentFolderId = getSelectedParentFolderId(
- fileTreeData,
- selectedEntityIds
- )
-
const error = validateCreate(fileTreeData, parentFolderId, entity)
if (error) {
return Promise.reject(error)
@@ -287,7 +259,7 @@ export function useFileTreeActionable() {
return syncCreateEntity(projectId, parentFolderId, entity)
},
- [fileTreeData, projectId, selectedEntityIds]
+ [fileTreeData, parentFolderId, projectId]
)
const finishCreatingFolder = useCallback(
@@ -301,15 +273,12 @@ export function useFileTreeActionable() {
dispatch({ type: ACTION_TYPES.ERROR, error })
})
},
- [dispatch, finishCreatingEntity]
+ [finishCreatingEntity]
)
- const startCreatingFile = useCallback(
- newFileCreateMode => {
- dispatch({ type: ACTION_TYPES.START_CREATE_FILE, newFileCreateMode })
- },
- [dispatch]
- )
+ const startCreatingFile = useCallback(newFileCreateMode => {
+ dispatch({ type: ACTION_TYPES.START_CREATE_FILE, newFileCreateMode })
+ }, [])
const startCreatingDocOrFile = useCallback(() => {
startCreatingFile('doc')
@@ -331,7 +300,7 @@ export function useFileTreeActionable() {
dispatch({ type: ACTION_TYPES.ERROR, error })
})
},
- [dispatch, finishCreatingEntity]
+ [finishCreatingEntity]
)
const finishCreatingDoc = useCallback(
@@ -352,28 +321,15 @@ export function useFileTreeActionable() {
const cancel = useCallback(() => {
dispatch({ type: ACTION_TYPES.CANCEL })
- }, [dispatch])
+ }, [])
- const parentFolderId = useMemo(
- () => getSelectedParentFolderId(fileTreeData, selectedEntityIds),
- [fileTreeData, selectedEntityIds]
- )
-
- return {
+ const value = {
canDelete: selectedEntityIds.size > 0,
canRename: selectedEntityIds.size === 1,
canCreate: selectedEntityIds.size < 2,
- isDeleting,
- isMoving,
- isRenaming,
- isCreatingFile,
- isCreatingFolder,
- inFlight,
- actionedEntities,
- error,
+ ...state,
parentFolderId,
isDuplicate,
- newFileCreateMode,
startRenaming,
finishRenaming,
startDeleting,
@@ -388,6 +344,32 @@ export function useFileTreeActionable() {
finishCreatingLinkedFile,
cancel,
}
+
+ return (
+
+ {children}
+
+ )
+}
+
+FileTreeActionableProvider.propTypes = {
+ hasWritePermissions: PropTypes.bool.isRequired,
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ ]).isRequired,
+}
+
+export function useFileTreeActionable() {
+ const context = useContext(FileTreeActionableContext)
+
+ if (!context) {
+ throw new Error(
+ 'useFileTreeActionable is only available inside FileTreeActionableProvider'
+ )
+ }
+
+ return context
}
function getSelectedParentFolderId(fileTreeData, selectedEntityIds) {
diff --git a/services/web/frontend/js/features/file-tree/contexts/file-tree-mutable.js b/services/web/frontend/js/features/file-tree/contexts/file-tree-mutable.js
index 394d960bcd..92b616441f 100644
--- a/services/web/frontend/js/features/file-tree/contexts/file-tree-mutable.js
+++ b/services/web/frontend/js/features/file-tree/contexts/file-tree-mutable.js
@@ -85,10 +85,62 @@ export const FileTreeMutableProvider = function ({ rootFolder, children }) {
}
)
+ const dispatchCreateFolder = useCallback((parentFolderId, entity) => {
+ entity.type = 'folder'
+ dispatch({
+ type: ACTION_TYPES.CREATE_ENTITY,
+ parentFolderId,
+ entity,
+ })
+ }, [])
+
+ const dispatchCreateDoc = useCallback((parentFolderId, entity) => {
+ entity.type = 'doc'
+ dispatch({
+ type: ACTION_TYPES.CREATE_ENTITY,
+ parentFolderId,
+ entity,
+ })
+ }, [])
+
+ const dispatchCreateFile = useCallback((parentFolderId, entity) => {
+ entity.type = 'fileRef'
+ dispatch({
+ type: ACTION_TYPES.CREATE_ENTITY,
+ parentFolderId,
+ entity,
+ })
+ }, [])
+
+ const dispatchRename = useCallback((id, newName) => {
+ dispatch({
+ type: ACTION_TYPES.RENAME,
+ newName,
+ id,
+ })
+ }, [])
+
+ const dispatchDelete = useCallback(id => {
+ dispatch({ type: ACTION_TYPES.DELETE, id })
+ }, [])
+
+ const dispatchMove = useCallback((entityId, toFolderId) => {
+ dispatch({ type: ACTION_TYPES.MOVE, entityId, toFolderId })
+ }, [])
+
+ const value = {
+ dispatchCreateDoc,
+ dispatchCreateFile,
+ dispatchCreateFolder,
+ dispatchDelete,
+ dispatchMove,
+ dispatchRename,
+ fileCount,
+ fileTreeData,
+ }
+
return (
-
+
{children}
)
@@ -103,81 +155,15 @@ FileTreeMutableProvider.propTypes = {
}
export function useFileTreeMutable() {
- const { fileTreeData, fileCount, dispatch } = useContext(
- FileTreeMutableContext
- )
+ const context = useContext(FileTreeMutableContext)
- const dispatchCreateFolder = useCallback(
- (parentFolderId, entity) => {
- entity.type = 'folder'
- dispatch({
- type: ACTION_TYPES.CREATE_ENTITY,
- parentFolderId,
- entity,
- })
- },
- [dispatch]
- )
-
- const dispatchCreateDoc = useCallback(
- (parentFolderId, entity) => {
- entity.type = 'doc'
- dispatch({
- type: ACTION_TYPES.CREATE_ENTITY,
- parentFolderId,
- entity,
- })
- },
- [dispatch]
- )
-
- const dispatchCreateFile = useCallback(
- (parentFolderId, entity) => {
- entity.type = 'fileRef'
- dispatch({
- type: ACTION_TYPES.CREATE_ENTITY,
- parentFolderId,
- entity,
- })
- },
- [dispatch]
- )
-
- const dispatchRename = useCallback(
- (id, newName) => {
- dispatch({
- type: ACTION_TYPES.RENAME,
- newName,
- id,
- })
- },
- [dispatch]
- )
-
- const dispatchDelete = useCallback(
- id => {
- dispatch({ type: ACTION_TYPES.DELETE, id })
- },
- [dispatch]
- )
-
- const dispatchMove = useCallback(
- (entityId, toFolderId) => {
- dispatch({ type: ACTION_TYPES.MOVE, entityId, toFolderId })
- },
- [dispatch]
- )
-
- return {
- fileTreeData,
- fileCount,
- dispatchRename,
- dispatchDelete,
- dispatchMove,
- dispatchCreateFolder,
- dispatchCreateDoc,
- dispatchCreateFile,
+ if (!context) {
+ throw new Error(
+ 'useFileTreeMutable is only available in FileTreeMutableProvider'
+ )
}
+
+ return context
}
function filesInFolder({ docs, folders, fileRefs }) {
diff --git a/services/web/frontend/js/features/file-tree/contexts/file-tree-selectable.js b/services/web/frontend/js/features/file-tree/contexts/file-tree-selectable.js
index 382602dc3b..3a1a27d5bb 100644
--- a/services/web/frontend/js/features/file-tree/contexts/file-tree-selectable.js
+++ b/services/web/frontend/js/features/file-tree/contexts/file-tree-selectable.js
@@ -141,10 +141,32 @@ export function FileTreeSelectableProvider({
return () => window.removeEventListener('editor.openDoc', handleOpenDoc)
}, [fileTreeData])
+ const select = useCallback(id => {
+ dispatch({ type: ACTION_TYPES.SELECT, id })
+ }, [])
+
+ const unselect = useCallback(id => {
+ dispatch({ type: ACTION_TYPES.UNSELECT, id })
+ }, [])
+
+ const selectOrMultiSelectEntity = useCallback((id, isMultiSelect) => {
+ const actionType = isMultiSelect
+ ? ACTION_TYPES.MULTI_SELECT
+ : ACTION_TYPES.SELECT
+
+ dispatch({ type: actionType, id })
+ }, [])
+
+ const value = {
+ selectedEntityIds,
+ selectedEntityParentIds,
+ select,
+ unselect,
+ selectOrMultiSelectEntity,
+ }
+
return (
-
+
{children}
)
@@ -161,44 +183,43 @@ FileTreeSelectableProvider.propTypes = {
}
export function useSelectableEntity(id) {
- const { selectedEntityIds, dispatch } = useContext(FileTreeSelectableContext)
+ const { selectedEntityIds, selectOrMultiSelectEntity } = useContext(
+ FileTreeSelectableContext
+ )
const isSelected = selectedEntityIds.has(id)
- const selectOrMultiSelectEntity = useCallback(
+ const handleEvent = useCallback(
ev => {
- const isMultiSelect = ev.ctrlKey || ev.metaKey
- const actionType = isMultiSelect
- ? ACTION_TYPES.MULTI_SELECT
- : ACTION_TYPES.SELECT
-
- dispatch({ type: actionType, id })
+ selectOrMultiSelectEntity(id, ev.ctrlKey || ev.metaKey)
},
- [dispatch, id]
+ [id, selectOrMultiSelectEntity]
)
const handleClick = useCallback(
ev => {
- selectOrMultiSelectEntity(ev)
+ handleEvent(ev)
},
- [selectOrMultiSelectEntity]
+ [handleEvent]
)
const handleKeyPress = useCallback(
ev => {
if (ev.key === 'Enter' || ev.key === ' ') {
- selectOrMultiSelectEntity(ev)
+ handleEvent(ev)
}
},
- [selectOrMultiSelectEntity]
+ [handleEvent]
)
const handleContextMenu = useCallback(
ev => {
// make sure the right-clicked entity gets selected
- if (!selectedEntityIds.has(id)) selectOrMultiSelectEntity(ev)
+ if (!selectedEntityIds.has(id)) {
+ handleEvent(ev)
+ }
},
- [id, selectOrMultiSelectEntity, selectedEntityIds]
+ [id, handleEvent, selectedEntityIds]
)
const props = useMemo(
@@ -216,28 +237,13 @@ export function useSelectableEntity(id) {
}
export function useFileTreeSelectable() {
- const { selectedEntityIds, selectedEntityParentIds, dispatch } = useContext(
- FileTreeSelectableContext
- )
+ const context = useContext(FileTreeSelectableContext)
- const select = useCallback(
- id => {
- dispatch({ type: ACTION_TYPES.SELECT, id })
- },
- [dispatch]
- )
-
- const unselect = useCallback(
- id => {
- dispatch({ type: ACTION_TYPES.UNSELECT, id })
- },
- [dispatch]
- )
-
- return {
- selectedEntityIds,
- selectedEntityParentIds,
- select,
- unselect,
+ if (!context) {
+ throw new Error(
+ `useFileTreeSelectable is only available inside FileTreeSelectableProvider`
+ )
}
+
+ return context
}