fix(#1197): 修复预览结果渲染 React Error #31 — 对象被直接渲染为 React child #1215

Merged
xiaoxia merged 1 commits from fix/1197-react-error-31 into develop 2026-08-02 08:25:58 +08:00
Owner

问题

Staging 验证 #1197 预览生成时,前端触发 React Error #31: "Objects are not valid as a React child (found: object with keys {})",整个应用崩溃。

根因

useStep3Preview.ts 轮询预览任务状态时,直接将 data.error_message 设为 state:

setPreviewError(data.error_message || "预览生成失败,请重试")

当后端返回的 error_message 是对象类型(如 {}{detail: "..."})时,Step3GeneratePreviewPreviewVideoPanel 组件将其作为 React child 直接渲染,触发 Error #31。

注意:useGenerationPolling.ts 已使用 safeExtractError 做了防护,但 useStep3Preview.ts 没有。

修复

  1. useStep3Preview.ts — 复用 safeExtractError,封装 safeString / safeNumber 工具函数:

    • error_message → 强制转为字符串
    • task_id, video_url → 强制转为字符串
    • clip_count, duration, file_size 等 → 强制转为数字
    • progress → 强制转为数字
    • catch 块中的异常也使用 safeString 处理
  2. Step3GeneratePreview.tsxpreviewError 增加 typeof === "string" 守卫

  3. PreviewVideoPanel.tsxpreviewError 增加 typeof === "string" 守卫

影响范围

  • 仅修改 pages/generate/ 下的 3 个文件
  • 向后兼容,不改变 API 接口
  • 现有 smoke test 通过
## 问题 Staging 验证 #1197 预览生成时,前端触发 **React Error #31**: "Objects are not valid as a React child (found: object with keys {})",整个应用崩溃。 ## 根因 `useStep3Preview.ts` 轮询预览任务状态时,直接将 `data.error_message` 设为 state: ```js setPreviewError(data.error_message || "预览生成失败,请重试") ``` 当后端返回的 `error_message` 是对象类型(如 `{}` 或 `{detail: "..."}`)时,`Step3GeneratePreview` 和 `PreviewVideoPanel` 组件将其作为 React child 直接渲染,触发 Error #31。 注意:`useGenerationPolling.ts` 已使用 `safeExtractError` 做了防护,但 `useStep3Preview.ts` 没有。 ## 修复 1. **useStep3Preview.ts** — 复用 `safeExtractError`,封装 `safeString` / `safeNumber` 工具函数: - `error_message` → 强制转为字符串 - `task_id`, `video_url` → 强制转为字符串 - `clip_count`, `duration`, `file_size` 等 → 强制转为数字 - `progress` → 强制转为数字 - catch 块中的异常也使用 `safeString` 处理 2. **Step3GeneratePreview.tsx** — `previewError` 增加 `typeof === "string"` 守卫 3. **PreviewVideoPanel.tsx** — `previewError` 增加 `typeof === "string"` 守卫 ## 影响范围 - 仅修改 `pages/generate/` 下的 3 个文件 - 向后兼容,不改变 API 接口 - 现有 smoke test 通过
xiaoxia added 1 commit 2026-08-02 08:18:00 +08:00
fix(#1197): 修复预览结果渲染 React Error #31 — 对象被直接渲染为 React child
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 33s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m1s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m2s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m44s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m19s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m59s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m38s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m16s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m46s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 4m0s
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 6s
AI Code Review / AI Code Review (pull_request) Successful in 4m29s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 39s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 45s
d8a4c21556
根因:useStep3Preview 轮询失败时直接将 data.error_message 设为 state,
当后端返回对象类型(如 {} 或 {detail: "..."})时,
Step3GeneratePreview / PreviewVideoPanel 将其作为 React child 渲染,
触发 Error #31 "Objects are not valid as a React child"。

修复:
1. useStep3Preview.ts — 复用 safeExtractError 封装 safeString/safeNumber,
   确保 error_message、task_id、video_url 等字段始终为字符串,
   clip_count、duration 等字段始终为数字
2. Step3GeneratePreview.tsx — previewError 增加 typeof 守卫
3. PreviewVideoPanel.tsx — previewError 增加 typeof 守卫

🚀 预览环境已部署

项目 详情
PR号 #1215
预览链接 https://pr-1215.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #1215 | | 预览链接 | [https://pr-1215.preview.xiaoxiajianji.com](https://pr-1215.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
auto-approve-bot approved these changes 2026-08-02 08:20:17 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
auto-approve-bot approved these changes 2026-08-02 08:20:17 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
Collaborator

代码审查结果 - PR #1215

⚠️ 问题(1个需要修改)

  1. apps/web/src/pages/generate/hooks/useStep3Preview.ts 第9-13行safeString 函数实现存在数据丢失风险
    • 问题描述:该函数直接对所有非空值调用 safeExtractError。如果传入的 val 是一个普通字符串(如 API 返回的 task_idvideo_url),而 safeExtractError 仅设计用于处理 Error 对象(可能对非 Error 类型返回 undefined 或空字符串),则会导致有效数据被 fallback 值(空字符串)覆盖。这会导致 taskId 丢失或 videoUrl 为空,进而导致功能异常(如无法播放视频、无法追踪任务)。
    • 修改建议:修改 safeString 逻辑,优先处理基本类型。例如:
      const safeString = (val: unknown, fallback: string): string => {
        if (val == null) return fallback
        if (typeof val === 'string') return val
        const s = safeExtractError(val)
        return s || fallback
      }
      

💡 建议(2个可选)

  1. apps/web/src/pages/generate/components/PreviewVideoPanel.tsx 第71-73行:冗余的类型检查
    • 具体内容useStep3Preview hook 中已经通过 safeString 确保 previewError 为字符串类型,组件中的 typeof previewError === "string" 检查是冗余的,可以直接使用 {previewError || "请重试"}(前提是修复了上述问题)。
  2. apps/web/src/pages/generate/components/Step3GeneratePreview.tsx 第89-91行:冗余的类型检查
    • 具体内容:同上,Hook 层已保证数据类型,组件层的防御性检查可以移除以简化代码。

格式检查通过 | 逻辑审查需修改 | 性能良好


🤖 由 AI 代码审查机器人自动生成 | 2026-08-02 00:22:31 | 模型:

## 代码审查结果 - PR #1215 ### ⚠️ 问题(1个需要修改) 1. **apps/web/src/pages/generate/hooks/useStep3Preview.ts 第9-13行**:`safeString` 函数实现存在数据丢失风险 - **问题描述**:该函数直接对所有非空值调用 `safeExtractError`。如果传入的 `val` 是一个普通字符串(如 API 返回的 `task_id` 或 `video_url`),而 `safeExtractError` 仅设计用于处理 Error 对象(可能对非 Error 类型返回 `undefined` 或空字符串),则会导致有效数据被 fallback 值(空字符串)覆盖。这会导致 `taskId` 丢失或 `videoUrl` 为空,进而导致功能异常(如无法播放视频、无法追踪任务)。 - **修改建议**:修改 `safeString` 逻辑,优先处理基本类型。例如: ```typescript const safeString = (val: unknown, fallback: string): string => { if (val == null) return fallback if (typeof val === 'string') return val const s = safeExtractError(val) return s || fallback } ``` ### 💡 建议(2个可选) 1. **apps/web/src/pages/generate/components/PreviewVideoPanel.tsx 第71-73行**:冗余的类型检查 - **具体内容**:`useStep3Preview` hook 中已经通过 `safeString` 确保 `previewError` 为字符串类型,组件中的 `typeof previewError === "string"` 检查是冗余的,可以直接使用 `{previewError || "请重试"}`(前提是修复了上述问题)。 2. **apps/web/src/pages/generate/components/Step3GeneratePreview.tsx 第89-91行**:冗余的类型检查 - **具体内容**:同上,Hook 层已保证数据类型,组件层的防御性检查可以移除以简化代码。 --- ✅ 格式检查通过 | ❌ 逻辑审查需修改 | ✅ 性能良好 --- <sub>🤖 由 AI 代码审查机器人自动生成 | 2026-08-02 00:22:31 | 模型: </sub> <!-- AI_CODE_REVIEW_AUTO_COMMENT -->
xiaoxia merged commit 62909de618 into develop 2026-08-02 08:25:58 +08:00
xiaoxia deleted branch fix/1197-react-error-31 2026-08-02 08:25:59 +08:00

🗑️ 预览环境已清理

PR #1215 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #1215 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.