fix: guard against Pydantic validation error objects in toast/error messages

FastAPI returns `detail` as an array of objects for 422 validation errors,
not a string. Passing these objects to toast.error() or rendering them in
JSX crashes React with Error #31 ("Objects are not valid as a React child").
Now checks typeof detail === 'string' before using it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-19 16:25:41 -05:00
parent 65b0514b40
commit 3dc635659f
2 changed files with 14 additions and 7 deletions

View File

@@ -25,7 +25,12 @@ function handleGlobalError(error: AxiosError) {
}
const status = error.response.status
const data = error.response.data as { detail?: string }
const data = error.response.data as { detail?: string | unknown[] }
// Extract a displayable error message from the response.
// FastAPI returns `detail` as a string for most errors, but 422 validation
// errors return an array of objects — we must not pass those to toast.
const detail = typeof data?.detail === 'string' ? data.detail : undefined
// Don't show toast for 401 (handled by refresh interceptor)
if (status === 401) {
@@ -34,13 +39,13 @@ function handleGlobalError(error: AxiosError) {
// Rate limit
if (status === 429) {
toast.error(data?.detail || 'Too many requests — please try again shortly')
toast.error(detail || 'Too many requests — please try again shortly')
return
}
// Client errors (4xx) — show backend detail if present
if (status >= 400 && status < 500) {
toast.error(data?.detail || 'Invalid request')
toast.error(detail || 'Invalid request')
return
}

View File

@@ -48,8 +48,9 @@ export const useAuthStore = create<AuthState>()(
// Fetch user info
await get().fetchUser()
} catch (error: unknown) {
const axiosErr = error as { response?: { data?: { detail?: string } } }
const message = axiosErr.response?.data?.detail || (error instanceof Error ? error.message : 'Login failed')
const axiosErr = error as { response?: { data?: { detail?: unknown } } }
const rawDetail = axiosErr.response?.data?.detail
const message = (typeof rawDetail === 'string' ? rawDetail : null) || (error instanceof Error ? error.message : 'Login failed')
set({ error: message, isLoading: false })
throw error
}
@@ -62,8 +63,9 @@ export const useAuthStore = create<AuthState>()(
// After registration, log the user in
await get().login({ email: data.email, password: data.password })
} catch (error: unknown) {
const axiosErr = error as { response?: { data?: { detail?: string } } }
const message = axiosErr.response?.data?.detail || (error instanceof Error ? error.message : 'Registration failed')
const axiosErr = error as { response?: { data?: { detail?: unknown } } }
const rawDetail = axiosErr.response?.data?.detail
const message = (typeof rawDetail === 'string' ? rawDetail : null) || (error instanceof Error ? error.message : 'Registration failed')
set({ error: message, isLoading: false })
throw error
}