Refactor error handling for profile mutations

This commit is contained in:
Joshen Lim committed 2023-07-11 21:22:48 +08:00
1 parent ed77f09070
commit cc320af73d
5 files changed
+111 -55

No files matched your search

+13 -3
View File
@@ -1,6 +1,9 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { toast } from 'react-hot-toast'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { ResponseError } from 'types'
import { profileKeys } from './keys'
import { Profile } from './types'
@@ -19,16 +22,23 @@ type ProfileCreateData = Awaited<ReturnType<typeof createProfile>>
export const useProfileCreateMutation = ({
onSuccess,
onError,
...options
}: Omit<UseMutationOptions<ProfileCreateData, unknown, void>, 'mutationFn'> = {}) => {
}: Omit<UseMutationOptions<ProfileCreateData, ResponseError, void>, 'mutationFn'> = {}) => {
const queryClient = useQueryClient()
return useMutation<ProfileCreateData, unknown, void>(() => createProfile(), {
return useMutation<ProfileCreateData, ResponseError, void>(() => createProfile(), {
async onSuccess(data, variables, context) {
await queryClient.invalidateQueries(profileKeys.profile())
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to create profile: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}
+4 -8
View File
@@ -4,25 +4,21 @@ import { API_URL } from 'lib/constants'
import { useCallback } from 'react'
import { profileKeys } from './keys'
import { Profile } from './types'
import { ResponseError } from 'types'
export type ProfileResponse = Profile
export async function getProfile(signal?: AbortSignal) {
const response = await get(`${API_URL}/profile`, {
signal,
headers: {
Version: '2',
},
headers: { Version: '2' },
})
if (response.error) {
throw response.error
}
if (response.error) throw response.error
return response as ProfileResponse
}
export type ProfileData = Awaited<ReturnType<typeof getProfile>>
export type ProfileError = unknown
export type ProfileError = ResponseError
export const useProfileQuery = <TData = ProfileData>({
enabled = true,
+13 -3
View File
@@ -1,6 +1,9 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { toast } from 'react-hot-toast'
import { patch } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { ResponseError } from 'types'
import { profileKeys } from './keys'
export type ProfileUpdateVariables = {
@@ -24,21 +27,28 @@ type ProfileUpdateData = Awaited<ReturnType<typeof updateProfile>>
export const useProfileUpdateMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseMutationOptions<ProfileUpdateData, unknown, ProfileUpdateVariables>,
UseMutationOptions<ProfileUpdateData, ResponseError, ProfileUpdateVariables>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<ProfileUpdateData, unknown, ProfileUpdateVariables>(
return useMutation<ProfileUpdateData, ResponseError, ProfileUpdateVariables>(
(vars) => updateProfile(vars),
{
async onSuccess(data, variables, context) {
await queryClient.invalidateQueries(profileKeys.profile())
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to create profile: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
}
)
+27 -3
View File
@@ -10,15 +10,22 @@ import { useProfileQuery } from 'data/profile/profile-query'
import { Profile } from 'data/profile/types'
import { useStore } from 'hooks'
import Telemetry from 'lib/telemetry'
import { ResponseError } from 'types'
export type ProfileContextType = {
profile: Profile | undefined
error: ResponseError | null
isLoading: boolean
isError: boolean
isSuccess: boolean
}
export const ProfileContext = createContext<ProfileContextType>({
profile: undefined,
error: null,
isLoading: true,
isError: false,
isSuccess: false,
})
export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => {
@@ -37,7 +44,7 @@ export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => {
await invalidateOrganizationsQuery(queryClient)
},
onError(err) {
onError() {
ui.setNotification({
category: 'error',
message: 'Failed to create your profile. Please refresh to try again.',
@@ -46,7 +53,13 @@ export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => {
})
// Track telemetry for the current user
const { data: profile, isLoading: isLoadingProfile } = useProfileQuery({
const {
error,
data: profile,
isLoading: isLoadingProfile,
isError,
isSuccess,
} = useProfileQuery({
onSuccess(profile) {
Telemetry.sendIdentify(profile, telemetryProps)
},
@@ -64,10 +77,21 @@ export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => {
const isLoading = isLoadingProfile || isCreatingProfile || isLoadingPermissions
return {
error,
profile,
isLoading,
isError,
isSuccess,
}
}, [isLoadingProfile, isCreatingProfile, isLoadingPermissions, profile])
}, [
isLoadingProfile,
isCreatingProfile,
isLoadingPermissions,
profile,
error,
isError,
isSuccess,
])
return <ProfileContext.Provider value={value}>{children}</ProfileContext.Provider>
}
+54 -38
View File
@@ -12,6 +12,8 @@ import { useSession } from 'lib/auth'
import { useProfile } from 'lib/profile'
import { NextPageWithLayout } from 'types'
import { Button, IconMoon, IconSun, Input, Listbox } from 'ui'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import AlertError from 'components/ui/AlertError'
const User: NextPageWithLayout = () => {
return (
@@ -39,53 +41,67 @@ export default User
const ProfileCard = observer(() => {
const { ui } = useStore()
const { mutateAsync } = useProfileUpdateMutation()
const { profile } = useProfile()
// TODO: ^ handle loading state
const updateUser = async (model: any) => {
try {
await mutateAsync({
firstName: model.first_name,
lastName: model.last_name,
})
ui.setNotification({ category: 'success', message: 'Successfully saved profile' })
} catch (error) {
const { profile, error, isLoading, isError, isSuccess } = useProfile()
const { mutateAsync: updateProfile } = useProfileUpdateMutation({
onError: (error) => {
ui.setNotification({
error,
category: 'error',
message: "Couldn't update profile. Please try again later.",
})
}
},
})
const updateUser = async (model: any) => {
await updateProfile({
firstName: model.first_name,
lastName: model.last_name,
})
ui.setNotification({ category: 'success', message: 'Successfully saved profile' })
}
return (
<article className="max-w-4xl p-4">
<section>
<Profile profile={profile} />
</section>
<section>
{/* @ts-ignore */}
<SchemaFormPanel
title="Profile"
schema={{
type: 'object',
required: [],
properties: {
first_name: { type: 'string' },
last_name: { type: 'string' },
},
}}
model={{
first_name: profile?.first_name ?? '',
last_name: profile?.last_name ?? '',
}}
onSubmit={updateUser}
/>
</section>
{isLoading && (
<Panel>
<div className="p-4">
<GenericSkeletonLoader />
</div>
</Panel>
)}
{isError && (
<Panel>
<div className="p-4">
<AlertError error={error} subject="Failed to retrieve account information" />
</div>
</Panel>
)}
{isSuccess && (
<>
<section>
<Profile profile={profile} />
</section>
<section>
{/* @ts-ignore */}
<SchemaFormPanel
title="Profile"
schema={{
type: 'object',
required: [],
properties: {
first_name: { type: 'string' },
last_name: { type: 'string' },
},
}}
model={{
first_name: profile?.first_name ?? '',
last_name: profile?.last_name ?? '',
}}
onSubmit={updateUser}
/>
</section>
</>
)}
<section>
<ThemeSettings />