Skip to content

Commit fdc7c5e

Browse files
fix: resolve runtime error in GraphSection and improve stability
- Fixed "Invalid value" runtime error by splitting GraphSection into static and streamed components - Improved detection of StreamableValue vs static DataAnalysisResult - Added graceful fallbacks for missing title, chartType, or plotData - Moved error messages out of ResponsiveContainer to fix layout issues - Verified fix with static, stringified, and broken data cases Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
1 parent 37d772b commit fdc7c5e

File tree

2 files changed

+121
-83
lines changed

2 files changed

+121
-83
lines changed

components/graph-section.tsx

Lines changed: 121 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -33,131 +33,169 @@ interface GraphSectionProps {
3333
}
3434

3535
export function GraphSection({ result }: GraphSectionProps) {
36-
// Check if result is a streamable value (has a value property or internal structure)
37-
// We use a heuristic or just try-catch if needed, but useStreamableValue must be called at the top level.
38-
// Actually, we can check if it looks like a streamable value.
39-
const isStreamable = result && typeof result === 'object' && ('value' in result || 'done' in result || (result as any)._isStreamable);
36+
if (!result) return null;
4037

41-
const [streamData, error, pending] = useStreamableValue(isStreamable ? (result as any) : undefined)
38+
// Check if result is a static DataAnalysisResult object
39+
// A StreamableValue is an opaque object and shouldn't have these properties
40+
const isStatic = typeof result === 'object' && result !== null &&
41+
('chartType' in (result as any) || 'title' in (result as any) || 'data' in (result as any));
42+
const isString = typeof result === 'string';
4243

43-
const data = isStreamable ? streamData : result;
44+
if (isStatic || isString) {
45+
return <GraphCard data={result as any} />;
46+
}
47+
48+
// Handle case where it might be a streamable value or something else
49+
// We use a safe wrapper to avoid crashing if useStreamableValue throws
50+
return <StreamedGraphSection result={result as any} />;
51+
}
4452

45-
const chartData: DataAnalysisResult | undefined = typeof data === 'string'
46-
? JSON.parse(data)
47-
: data as DataAnalysisResult
53+
function StreamedGraphSection({ result }: { result: StreamableValue<any> }) {
54+
const [data, error, pending] = useStreamableValue(result);
4855

49-
if (pending && !chartData) {
56+
if (pending && !data) {
5057
return (
5158
<Section className="py-2">
5259
<div className="animate-pulse flex space-y-4 flex-col">
5360
<div className="h-4 bg-muted rounded w-3/4"></div>
5461
<div className="h-64 bg-muted rounded"></div>
5562
</div>
5663
</Section>
57-
)
64+
);
5865
}
5966

60-
if (!chartData) return null
67+
return <GraphCard data={data} />;
68+
}
69+
70+
function GraphCard({ data, pending }: { data: any, pending?: boolean }) {
71+
const chartData: DataAnalysisResult | undefined = React.useMemo(() => {
72+
if (!data) return undefined;
73+
if (typeof data === 'string') {
74+
try {
75+
return JSON.parse(data);
76+
} catch (e) {
77+
console.error('Error parsing graph data:', e);
78+
return undefined;
79+
}
80+
}
81+
return data as DataAnalysisResult;
82+
}, [data]);
83+
84+
if (!chartData) return null;
6185

62-
const { title, description, chartType, data: plotData, config } = chartData
86+
const { title, description, chartType, data: plotData, config } = chartData;
6387

6488
const renderChart = () => {
89+
if (!plotData || !config) return <div className="flex items-center justify-center h-full text-muted-foreground italic">Missing chart data or configuration</div>;
90+
6591
switch (chartType) {
6692
case 'bar':
6793
return (
68-
<BarChart data={plotData}>
69-
<CartesianGrid strokeDasharray="3 3" />
70-
<XAxis dataKey={config.xAxisKey} />
71-
<YAxis />
72-
<Tooltip />
73-
<Legend />
74-
{config.series.map((s, i) => (
75-
<Bar key={s.key} dataKey={s.key} name={s.name} fill={s.color || COLORS[i % COLORS.length]} />
76-
))}
77-
</BarChart>
78-
)
94+
<ResponsiveContainer width="100%" height="100%">
95+
<BarChart data={plotData}>
96+
<CartesianGrid strokeDasharray="3 3" />
97+
<XAxis dataKey={config.xAxisKey} />
98+
<YAxis />
99+
<Tooltip />
100+
<Legend />
101+
{config.series?.map((s, i) => (
102+
<Bar key={s.key} dataKey={s.key} name={s.name} fill={s.color || COLORS[i % COLORS.length]} />
103+
))}
104+
</BarChart>
105+
</ResponsiveContainer>
106+
);
79107
case 'line':
80108
return (
81-
<LineChart data={plotData}>
82-
<CartesianGrid strokeDasharray="3 3" />
83-
<XAxis dataKey={config.xAxisKey} />
84-
<YAxis />
85-
<Tooltip />
86-
<Legend />
87-
{config.series.map((s, i) => (
88-
<Line key={s.key} type="monotone" dataKey={s.key} name={s.name} stroke={s.color || COLORS[i % COLORS.length]} />
89-
))}
90-
</LineChart>
91-
)
109+
<ResponsiveContainer width="100%" height="100%">
110+
<LineChart data={plotData}>
111+
<CartesianGrid strokeDasharray="3 3" />
112+
<XAxis dataKey={config.xAxisKey} />
113+
<YAxis />
114+
<Tooltip />
115+
<Legend />
116+
{config.series?.map((s, i) => (
117+
<Line key={s.key} type="monotone" dataKey={s.key} name={s.name} stroke={s.color || COLORS[i % COLORS.length]} />
118+
))}
119+
</LineChart>
120+
</ResponsiveContainer>
121+
);
92122
case 'area':
93123
return (
94-
<AreaChart data={plotData}>
95-
<CartesianGrid strokeDasharray="3 3" />
96-
<XAxis dataKey={config.xAxisKey} />
97-
<YAxis />
98-
<Tooltip />
99-
<Legend />
100-
{config.series.map((s, i) => (
101-
<Area key={s.key} type="monotone" dataKey={s.key} name={s.name} stroke={s.color || COLORS[i % COLORS.length]} fill={s.color || COLORS[i % COLORS.length]} />
102-
))}
103-
</AreaChart>
104-
)
124+
<ResponsiveContainer width="100%" height="100%">
125+
<AreaChart data={plotData}>
126+
<CartesianGrid strokeDasharray="3 3" />
127+
<XAxis dataKey={config.xAxisKey} />
128+
<YAxis />
129+
<Tooltip />
130+
<Legend />
131+
{config.series?.map((s, i) => (
132+
<Area key={s.key} type="monotone" dataKey={s.key} name={s.name} stroke={s.color || COLORS[i % COLORS.length]} fill={s.color || COLORS[i % COLORS.length]} />
133+
))}
134+
</AreaChart>
135+
</ResponsiveContainer>
136+
);
105137
case 'pie':
106138
return (
107-
<PieChart>
108-
<Pie
109-
data={plotData}
110-
dataKey={config.series[0].key}
111-
nameKey={config.xAxisKey}
112-
cx="50%"
113-
cy="50%"
114-
outerRadius={80}
115-
label
116-
>
117-
{plotData.map((entry, index) => (
118-
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
119-
))}
120-
</Pie>
121-
<Tooltip />
122-
<Legend />
123-
</PieChart>
124-
)
139+
<ResponsiveContainer width="100%" height="100%">
140+
<PieChart>
141+
<Pie
142+
data={plotData}
143+
dataKey={config.series?.[0]?.key}
144+
nameKey={config.xAxisKey}
145+
cx="50%"
146+
cy="50%"
147+
outerRadius={80}
148+
label
149+
>
150+
{plotData.map((entry, index) => (
151+
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
152+
))}
153+
</Pie>
154+
<Tooltip />
155+
<Legend />
156+
</PieChart>
157+
</ResponsiveContainer>
158+
);
125159
case 'scatter':
126160
return (
127-
<ScatterChart>
128-
<CartesianGrid strokeDasharray="3 3" />
129-
<XAxis type="number" dataKey={config.xAxisKey} name={config.xAxisKey} />
130-
<YAxis type="number" dataKey={config.yAxisKey} name={config.yAxisKey} />
131-
<Tooltip cursor={{ strokeDasharray: '3 3' }} />
132-
<Legend />
133-
{config.series.map((s, i) => (
134-
<Scatter key={s.key} name={s.name} data={plotData} fill={s.color || COLORS[i % COLORS.length]} />
135-
))}
136-
</ScatterChart>
137-
)
161+
<ResponsiveContainer width="100%" height="100%">
162+
<ScatterChart>
163+
<CartesianGrid strokeDasharray="3 3" />
164+
<XAxis type="number" dataKey={config.xAxisKey} name={config.xAxisKey} />
165+
<YAxis type="number" dataKey={config.yAxisKey} name={config.yAxisKey} />
166+
<Tooltip cursor={{ strokeDasharray: '3 3' }} />
167+
<Legend />
168+
{config.series?.map((s, i) => (
169+
<Scatter key={s.key} name={s.name} data={plotData} fill={s.color || COLORS[i % COLORS.length]} />
170+
))}
171+
</ScatterChart>
172+
</ResponsiveContainer>
173+
);
138174
default:
139-
return <div>Unsupported chart type: {chartType}</div>
175+
return (
176+
<div className="flex items-center justify-center h-full text-muted-foreground">
177+
Unsupported chart type: {chartType || 'None'}
178+
</div>
179+
);
140180
}
141-
}
181+
};
142182

143183
return (
144184
<Section className="py-2">
145185
<div className="mb-2">
146-
<ToolBadge tool="dataAnalysis">Graph: {title}</ToolBadge>
186+
<ToolBadge tool="dataAnalysis">Graph: {title || 'Untitled'}</ToolBadge>
147187
</div>
148188
<Card>
149189
<CardHeader className="pb-2">
150-
<CardTitle className="text-lg font-medium">{title}</CardTitle>
190+
<CardTitle className="text-lg font-medium">{title || 'Data Analysis'}</CardTitle>
151191
{description && <CardDescription>{description}</CardDescription>}
152192
</CardHeader>
153193
<CardContent>
154194
<div className="h-[300px] w-full">
155-
<ResponsiveContainer width="100%" height="100%">
156-
{renderChart()}
157-
</ResponsiveContainer>
195+
{renderChart()}
158196
</div>
159197
</CardContent>
160198
</Card>
161199
</Section>
162-
)
200+
);
163201
}

verification/fix_verification.png

61.4 KB
Loading

0 commit comments

Comments
 (0)