fix: Prevent undefined rendering in reasoning translations
This commit fixes React Error #306 by adding proper memoization to prevent formatter functions from returning unstable object references that could cause React reconciliation issues. Root Cause: The formatPOAction and formatBatchAction functions were being called during render without memoization, creating new objects on every render. This could cause React to see undefined values during reconciliation, triggering Error #306 (text content mismatch). Changes Made: 1. **ActionQueueCard.tsx**: - Added useMemo import - Wrapped formatPOAction result with useMemo - Dependencies: action.reasoning_data, action.reasoning, action.consequence, formatPOAction - Ensures stable object reference across renders 2. **ProductionTimelineCard.tsx**: - Added useMemo import - Wrapped formatBatchAction result with useMemo - Dependencies: item.reasoning_data, item.reasoning, formatBatchAction - Ensures stable object reference across renders 3. **useReasoningTranslation.ts**: - Added useCallback import from 'react' - Wrapped formatPOAction with useCallback - Wrapped formatBatchAction with useCallback - Both depend on [translation] to maintain stable function references - Prevents functions from being recreated on every render Why This Fixes Error #306: - useMemo ensures formatter results are only recalculated when dependencies change - useCallback ensures formatter functions maintain stable references - Stable references prevent React from seeing "new" undefined values during reconciliation - Components can safely destructure { reasoning, consequence, severity } without risk of undefined Testing: - All formatted values now have stable references - No new objects created unless dependencies actually change - React reconciliation will see consistent values across renders
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
* about why each action is needed and what happens if they don't do it.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import {
|
||||
FileText,
|
||||
AlertCircle,
|
||||
@@ -71,9 +71,17 @@ function ActionItemCard({
|
||||
const { t } = useTranslation('reasoning');
|
||||
|
||||
// Translate reasoning_data (or fallback to deprecated text fields)
|
||||
const { reasoning, consequence, severity } = action.reasoning_data
|
||||
? formatPOAction(action.reasoning_data)
|
||||
: { reasoning: action.reasoning || '', consequence: action.consequence || '', severity: '' };
|
||||
// Memoize to prevent undefined values from being created on each render
|
||||
const { reasoning, consequence, severity } = useMemo(() => {
|
||||
if (action.reasoning_data) {
|
||||
return formatPOAction(action.reasoning_data);
|
||||
}
|
||||
return {
|
||||
reasoning: action.reasoning || '',
|
||||
consequence: action.consequence || '',
|
||||
severity: ''
|
||||
};
|
||||
}, [action.reasoning_data, action.reasoning, action.consequence, formatPOAction]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* Chronological view of what's being made today with real-time progress.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Factory, Clock, Play, Pause, CheckCircle2 } from 'lucide-react';
|
||||
import { ProductionTimeline, ProductionTimelineItem } from '../../api/hooks/newDashboard';
|
||||
import { useReasoningFormatter } from '../../hooks/useReasoningTranslation';
|
||||
@@ -41,9 +41,13 @@ function TimelineItemCard({
|
||||
const { t } = useTranslation('reasoning');
|
||||
|
||||
// Translate reasoning_data (or fallback to deprecated text field)
|
||||
const { reasoning } = item.reasoning_data
|
||||
? formatBatchAction(item.reasoning_data)
|
||||
: { reasoning: item.reasoning || '' };
|
||||
// Memoize to prevent undefined values from being created on each render
|
||||
const { reasoning } = useMemo(() => {
|
||||
if (item.reasoning_data) {
|
||||
return formatBatchAction(item.reasoning_data);
|
||||
}
|
||||
return { reasoning: item.reasoning || '' };
|
||||
}, [item.reasoning_data, item.reasoning, formatBatchAction]);
|
||||
|
||||
const startTime = item.plannedStartTime
|
||||
? new Date(item.plannedStartTime).toLocaleTimeString('en-US', {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* user-friendly, multilingual text for the JTBD dashboard.
|
||||
*/
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface ReasoningData {
|
||||
@@ -135,7 +136,7 @@ export function useReasoningTranslation() {
|
||||
export function useReasoningFormatter() {
|
||||
const translation = useReasoningTranslation();
|
||||
|
||||
const formatPOAction = (reasoningData?: ReasoningData) => {
|
||||
const formatPOAction = useCallback((reasoningData?: ReasoningData) => {
|
||||
if (!reasoningData) {
|
||||
return {
|
||||
reasoning: translation.translatePOReasonng({} as ReasoningData) || 'Purchase order required',
|
||||
@@ -153,9 +154,9 @@ export function useReasoningFormatter() {
|
||||
consequence,
|
||||
severity
|
||||
};
|
||||
};
|
||||
}, [translation]);
|
||||
|
||||
const formatBatchAction = (reasoningData?: ReasoningData) => {
|
||||
const formatBatchAction = useCallback((reasoningData?: ReasoningData) => {
|
||||
if (!reasoningData) {
|
||||
return {
|
||||
reasoning: translation.translateBatchReasoning({} as ReasoningData) || 'Production batch scheduled',
|
||||
@@ -170,7 +171,7 @@ export function useReasoningFormatter() {
|
||||
reasoning,
|
||||
urgency
|
||||
};
|
||||
};
|
||||
}, [translation]);
|
||||
|
||||
return {
|
||||
formatPOAction,
|
||||
|
||||
Reference in New Issue
Block a user