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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user