overleaf/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js
Alf Eaton eebeffc1c5 Merge pull request #4188 from overleaf/ae-memo
Improve React performance by memoizing components and values

GitOrigin-RevId: 805278b8b7ac04c3dc4b078fa53cc0e3770d261b
2021-06-22 02:07:08 +00:00

172 lines
3.7 KiB
JavaScript

import React, {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from 'react'
import PropTypes from 'prop-types'
import ShareProjectModalContent from './share-project-modal-content'
import useScopeValue from '../../../shared/context/util/scope-value-hook'
const ShareProjectContext = createContext()
ShareProjectContext.Provider.propTypes = {
value: PropTypes.shape({
isAdmin: PropTypes.bool.isRequired,
updateProject: PropTypes.func.isRequired,
monitorRequest: PropTypes.func.isRequired,
inFlight: PropTypes.bool,
setInFlight: PropTypes.func,
error: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
setError: PropTypes.func,
}),
}
export function useShareProjectContext() {
const context = useContext(ShareProjectContext)
if (!context) {
throw new Error(
'useShareProjectContext is only available inside ShareProjectProvider'
)
}
return context
}
const projectShape = PropTypes.shape({
_id: PropTypes.string.isRequired,
members: PropTypes.arrayOf(
PropTypes.shape({
_id: PropTypes.string.isRequired,
})
),
invites: PropTypes.arrayOf(
PropTypes.shape({
_id: PropTypes.string.isRequired,
})
),
name: PropTypes.string,
features: PropTypes.shape({
collaborators: PropTypes.number,
}),
publicAccesLevel: PropTypes.string,
tokens: PropTypes.shape({
readOnly: PropTypes.string,
readAndWrite: PropTypes.string,
}),
owner: PropTypes.shape({
email: PropTypes.string,
}),
})
const ProjectContext = createContext()
ProjectContext.Provider.propTypes = {
value: projectShape,
}
export function useProjectContext() {
const context = useContext(ProjectContext)
if (!context) {
throw new Error(
'useProjectContext is only available inside ShareProjectProvider'
)
}
return context
}
const ShareProjectModal = React.memo(function ShareProjectModal({
handleHide,
show,
animation = true,
isAdmin,
}) {
const [inFlight, setInFlight] = useState(false)
const [error, setError] = useState()
const [project, setProject] = useScopeValue('project', true)
// reset error when the modal is opened
useEffect(() => {
if (show) {
setError(undefined)
}
}, [show])
// close the modal if not in flight
const cancel = useCallback(() => {
if (!inFlight) {
handleHide()
}
}, [handleHide, inFlight])
// update `error` and `inFlight` while sending a request
const monitorRequest = useCallback(request => {
setError(undefined)
setInFlight(true)
const promise = request()
promise.catch(error => {
setError(
error.data?.errorReason ||
error.data?.error ||
'generic_something_went_wrong'
)
})
promise.finally(() => {
setInFlight(false)
})
return promise
}, [])
// merge the new data with the old project data
const updateProject = useCallback(
data => {
setProject(project => Object.assign(project, data))
},
[setProject]
)
if (!project) {
return null
}
return (
<ShareProjectContext.Provider
value={{
isAdmin,
updateProject,
monitorRequest,
inFlight,
setInFlight,
error,
setError,
}}
>
<ProjectContext.Provider value={project}>
<ShareProjectModalContent
animation={animation}
cancel={cancel}
error={error}
inFlight={inFlight}
show={show}
/>
</ProjectContext.Provider>
</ShareProjectContext.Provider>
)
})
ShareProjectModal.propTypes = {
animation: PropTypes.bool,
handleHide: PropTypes.func.isRequired,
isAdmin: PropTypes.bool.isRequired,
show: PropTypes.bool.isRequired,
}
export default ShareProjectModal