mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-12 01:54:02 +00:00
Merge pull request #7941 from overleaf/msm-use-async-typing-fix
Fix typescript errors related to `useAsync()` hook GitOrigin-RevId: eacf7ec7e9c5da38ad88fde225ffedbfd1c2ff61
This commit is contained in:
parent
5731463d32
commit
4209828a6a
10 changed files with 77 additions and 25 deletions
|
@ -7,7 +7,10 @@ import {
|
|||
FormGroup,
|
||||
} from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { postJSON } from '../../../infrastructure/fetch-json'
|
||||
import {
|
||||
getUserFacingMessage,
|
||||
postJSON,
|
||||
} from '../../../infrastructure/fetch-json'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import { ExposedSettings } from '../../../../../types/exposed-settings'
|
||||
import useAsync from '../../../shared/hooks/use-async'
|
||||
|
@ -109,7 +112,7 @@ function AccountInfoSection() {
|
|||
) : null}
|
||||
{isError ? (
|
||||
<FormGroup>
|
||||
<Alert bsStyle="danger">{error.getUserFacingMessage()}</Alert>
|
||||
<Alert bsStyle="danger">{getUserFacingMessage(error)}</Alert>
|
||||
</FormGroup>
|
||||
) : null}
|
||||
{canUpdateEmail || canUpdateNames ? (
|
||||
|
|
|
@ -49,7 +49,7 @@ function InstitutionFields({
|
|||
const [isInstitutionFieldsVisible, setIsInstitutionFieldsVisible] =
|
||||
useState(false)
|
||||
const [isUniversityDirty, setIsUniversityDirty] = useState(false)
|
||||
const { runAsync: institutionRunAsync } = useAsync()
|
||||
const { runAsync: institutionRunAsync } = useAsync<University[]>()
|
||||
|
||||
useEffect(() => {
|
||||
if (isInstitutionFieldsVisible && countryRef.current) {
|
||||
|
@ -93,7 +93,7 @@ function InstitutionFields({
|
|||
return
|
||||
}
|
||||
|
||||
institutionRunAsync<University[]>(
|
||||
institutionRunAsync(
|
||||
getJSON(`/institutions/list?country_code=${countryCode}`)
|
||||
)
|
||||
.then(data => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Row, Alert } from 'react-bootstrap'
|
||||
import Icon from '../../../../../shared/components/icon'
|
||||
import { UseAsyncReturnType } from '../../../../../shared/hooks/use-async'
|
||||
import { getUserFacingMessage } from '../../../../../infrastructure/fetch-json'
|
||||
|
||||
type LayoutProps = {
|
||||
children: React.ReactNode
|
||||
|
@ -14,7 +15,7 @@ function Layout({ isError, error, children }: LayoutProps) {
|
|||
<Row>{children}</Row>
|
||||
{isError && (
|
||||
<Alert bsStyle="danger" className="text-center">
|
||||
<Icon type="exclamation-triangle" fw /> {error.getUserFacingMessage()}
|
||||
<Icon type="exclamation-triangle" fw /> {getUserFacingMessage(error)}
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,7 @@ type InstitutionAndRoleProps = {
|
|||
function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
|
||||
const { t } = useTranslation()
|
||||
const { isLoading, isError, runAsync } = useAsync()
|
||||
const changeAffiliationAsync = useAsync()
|
||||
const changeAffiliationAsync = useAsync<University>()
|
||||
const { affiliation } = userEmailData
|
||||
const {
|
||||
state,
|
||||
|
@ -55,9 +55,7 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
|
|||
}
|
||||
|
||||
changeAffiliationAsync
|
||||
.runAsync<University>(
|
||||
getJSON(`/institutions/list/${affiliation.institution.id}`)
|
||||
)
|
||||
.runAsync(getJSON(`/institutions/list/${affiliation.institution.id}`))
|
||||
.then(data => {
|
||||
if (data.departments.length) {
|
||||
setDepartments(data.departments)
|
||||
|
|
|
@ -7,12 +7,21 @@ import {
|
|||
FormGroup,
|
||||
} from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { postJSON } from '../../../infrastructure/fetch-json'
|
||||
import {
|
||||
getUserFacingMessage,
|
||||
postJSON,
|
||||
} from '../../../infrastructure/fetch-json'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import { ExposedSettings } from '../../../../../types/exposed-settings'
|
||||
import { PasswordStrengthOptions } from '../../../../../types/password-strength-options'
|
||||
import useAsync from '../../../shared/hooks/use-async'
|
||||
|
||||
type PasswordUpdateResult = {
|
||||
message?: {
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
function PasswordSection() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
@ -58,7 +67,8 @@ function PasswordForm() {
|
|||
const [currentPassword, setCurrentPassword] = useState('')
|
||||
const [newPassword1, setNewPassword1] = useState('')
|
||||
const [newPassword2, setNewPassword2] = useState('')
|
||||
const { isLoading, isSuccess, isError, data, error, runAsync } = useAsync()
|
||||
const { isLoading, isSuccess, isError, data, error, runAsync } =
|
||||
useAsync<PasswordUpdateResult>()
|
||||
const [isNewPasswordValid, setIsNewPasswordValid] = useState(false)
|
||||
const [isFormValid, setIsFormValid] = useState(false)
|
||||
|
||||
|
@ -128,7 +138,7 @@ function PasswordForm() {
|
|||
) : null}
|
||||
{isError ? (
|
||||
<FormGroup>
|
||||
<Alert bsStyle="danger">{error.getUserFacingMessage()}</Alert>
|
||||
<Alert bsStyle="danger">{getUserFacingMessage(error)}</Alert>
|
||||
</FormGroup>
|
||||
) : null}
|
||||
<Button
|
||||
|
|
|
@ -203,10 +203,11 @@ function useUserEmails() {
|
|||
)
|
||||
const [state, unsafeDispatch] = useReducer(reducer, initialState)
|
||||
const dispatch = useSafeDispatch(unsafeDispatch)
|
||||
const { data, isLoading, isError, isSuccess, runAsync } = useAsync()
|
||||
const { data, isLoading, isError, isSuccess, runAsync } =
|
||||
useAsync<UserEmailData[]>()
|
||||
|
||||
const getEmails = useCallback(() => {
|
||||
runAsync(getJSON<UserEmailData[]>('/user/emails?ensureAffiliation=true'))
|
||||
runAsync(getJSON('/user/emails?ensureAffiliation=true'))
|
||||
.then(data => {
|
||||
dispatch(ActionCreators.setData(data))
|
||||
})
|
||||
|
|
|
@ -207,3 +207,12 @@ async function parseResponseBody(response: Response) {
|
|||
// responses) or unsupported
|
||||
return {}
|
||||
}
|
||||
|
||||
export function getUserFacingMessage(error: Error): string {
|
||||
if (error instanceof FetchError) {
|
||||
return error.getUserFacingMessage()
|
||||
} else {
|
||||
// checking existence of `error` to prevent errors when called from Javascript
|
||||
return error?.message
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,34 +2,38 @@ import * as React from 'react'
|
|||
import useSafeDispatch from './use-safe-dispatch'
|
||||
import { Nullable } from '../../../../types/utils'
|
||||
|
||||
type State = {
|
||||
type State<T> = {
|
||||
status: 'idle' | 'pending' | 'resolved' | 'rejected'
|
||||
data: Nullable<unknown>
|
||||
error: Nullable<Record<string, unknown>>
|
||||
data: Nullable<T>
|
||||
error: Nullable<Error>
|
||||
}
|
||||
type Action = Partial<State>
|
||||
type Action<T> = Partial<State<T>>
|
||||
|
||||
const defaultInitialState: State = { status: 'idle', data: null, error: null }
|
||||
const defaultInitialState: State<null> = {
|
||||
status: 'idle',
|
||||
data: null,
|
||||
error: null,
|
||||
}
|
||||
|
||||
function useAsync(initialState?: Partial<State>) {
|
||||
function useAsync<T = any>(initialState?: Partial<State<T>>) {
|
||||
const initialStateRef = React.useRef({
|
||||
...defaultInitialState,
|
||||
...initialState,
|
||||
})
|
||||
const [{ status, data, error }, setState] = React.useReducer(
|
||||
(state: State, action: Action) => ({ ...state, ...action }),
|
||||
(state: State<T>, action: Action<T>) => ({ ...state, ...action }),
|
||||
initialStateRef.current
|
||||
)
|
||||
|
||||
const safeSetState = useSafeDispatch(setState)
|
||||
|
||||
const setData = React.useCallback(
|
||||
data => safeSetState({ data, status: 'resolved' }),
|
||||
(data: Nullable<T>) => safeSetState({ data, status: 'resolved' }),
|
||||
[safeSetState]
|
||||
)
|
||||
|
||||
const setError = React.useCallback(
|
||||
error => safeSetState({ error, status: 'rejected' }),
|
||||
(error: Nullable<Error>) => safeSetState({ error, status: 'rejected' }),
|
||||
[safeSetState]
|
||||
)
|
||||
|
||||
|
@ -39,7 +43,7 @@ function useAsync(initialState?: Partial<State>) {
|
|||
)
|
||||
|
||||
const runAsync = React.useCallback(
|
||||
<T>(promise: Promise<T>) => {
|
||||
(promise: Promise<T>) => {
|
||||
safeSetState({ status: 'pending' })
|
||||
|
||||
return promise.then(
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Response } from 'node-fetch'
|
|||
import {
|
||||
deleteJSON,
|
||||
FetchError,
|
||||
getUserFacingMessage,
|
||||
getJSON,
|
||||
postJSON,
|
||||
putJSON,
|
||||
|
@ -190,4 +191,29 @@ describe('fetchJSON', function () {
|
|||
|
||||
return expect(deleteJSON('/test')).to.eventually.deep.equal({})
|
||||
})
|
||||
|
||||
describe('getUserFacingMessage()', function () {
|
||||
it('returns the error facing message for FetchError instances', function () {
|
||||
const error = new FetchError(
|
||||
'403 error',
|
||||
'http:/example.com',
|
||||
{},
|
||||
{ status: 403 }
|
||||
)
|
||||
expect(getUserFacingMessage(error)).to.equal(
|
||||
'Session error. Please check you have cookies enabled. If the problem persists, try clearing your cache and cookies.'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns `message` for Error instances different than FetchError', function () {
|
||||
const error = new Error('403 error')
|
||||
expect(getUserFacingMessage(error)).to.equal('403 error')
|
||||
})
|
||||
|
||||
it('returns `undefined` for non-Error instances', function () {
|
||||
expect(getUserFacingMessage(undefined)).to.be.undefined
|
||||
expect(getUserFacingMessage(null)).to.be.undefined
|
||||
expect(getUserFacingMessage('error')).to.be.undefined
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -150,7 +150,7 @@ describe('useAsync', function () {
|
|||
})
|
||||
|
||||
it('can set the error', function () {
|
||||
const mockError = Symbol('rejected value')
|
||||
const mockError = new Error('rejected value')
|
||||
const { result } = renderHook(() => useAsync())
|
||||
|
||||
act(() => {
|
||||
|
|
Loading…
Add table
Reference in a new issue