Skip to content

Commit c7b0130

Browse files
niieanibbrzoska
authored andcommitted
fix: defer setState calls so they don’t fire synchronously during hook’s render‐time
fix for Warning: Cannot update a component (`TraceManagerDebugger`) while rendering a different component (`TicketView`). To locate the bad setState() call inside `TicketView`.
1 parent f22a7e3 commit c7b0130

File tree

1 file changed

+134
-118
lines changed

1 file changed

+134
-118
lines changed

src/v3/TraceManagerDebugger.tsx

Lines changed: 134 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,9 @@ export default function TraceManagerDebugger<
11621162
}, [float, isDragging, dragOffset])
11631163

11641164
useEffect(() => {
1165+
// schedule updates asynchronously so we never call setState mid‐render
1166+
const schedule = (fn: () => void) => void setTimeout(fn, 0)
1167+
11651168
// Subscribe to trace-start events
11661169
const startSub = traceManager.when('trace-start').subscribe((event) => {
11671170
const trace = event.traceContext as AllPossibleTraces<RelationSchemasT>
@@ -1208,7 +1211,7 @@ export default function TraceManagerDebugger<
12081211
),
12091212
}
12101213

1211-
setCurrentTrace(traceInfo)
1214+
schedule(() => void setCurrentTrace(traceInfo))
12121215
})
12131216

12141217
// Subscribe to state transition events
@@ -1218,62 +1221,65 @@ export default function TraceManagerDebugger<
12181221
const trace = event.traceContext as AllPossibleTraces<RelationSchemasT>
12191222
const transition = event.stateTransition
12201223

1221-
setCurrentTrace((prevTrace) => {
1222-
if (!prevTrace || prevTrace.traceId !== trace.input.id)
1223-
return prevTrace
1224-
1225-
const updatedTrace: TraceInfo<RelationSchemasT> = {
1226-
...prevTrace,
1227-
state: transition.transitionToState,
1228-
attributes: trace.input.attributes
1229-
? { ...trace.input.attributes }
1230-
: undefined,
1231-
relatedTo: trace.input.relatedTo
1232-
? { ...trace.input.relatedTo }
1233-
: undefined,
1234-
}
1224+
schedule(
1225+
() =>
1226+
void setCurrentTrace((prevTrace) => {
1227+
if (!prevTrace || prevTrace.traceId !== trace.input.id)
1228+
return prevTrace
1229+
1230+
const updatedTrace: TraceInfo<RelationSchemasT> = {
1231+
...prevTrace,
1232+
state: transition.transitionToState,
1233+
attributes: trace.input.attributes
1234+
? { ...trace.input.attributes }
1235+
: undefined,
1236+
relatedTo: trace.input.relatedTo
1237+
? { ...trace.input.relatedTo }
1238+
: undefined,
1239+
}
12351240

1236-
if ('interruptionReason' in transition) {
1237-
updatedTrace.interruptionReason = transition.interruptionReason
1238-
}
1241+
if ('interruptionReason' in transition) {
1242+
updatedTrace.interruptionReason = transition.interruptionReason
1243+
}
12391244

1240-
if (
1241-
'lastRequiredSpanAndAnnotation' in transition &&
1242-
transition.lastRequiredSpanAndAnnotation
1243-
) {
1244-
updatedTrace.lastRequiredSpanOffset =
1245-
transition.lastRequiredSpanAndAnnotation.annotation.operationRelativeEndTime
1246-
}
1245+
if (
1246+
'lastRequiredSpanAndAnnotation' in transition &&
1247+
transition.lastRequiredSpanAndAnnotation
1248+
) {
1249+
updatedTrace.lastRequiredSpanOffset =
1250+
transition.lastRequiredSpanAndAnnotation.annotation.operationRelativeEndTime
1251+
}
12471252

1248-
if (
1249-
'completeSpanAndAnnotation' in transition &&
1250-
transition.completeSpanAndAnnotation
1251-
) {
1252-
updatedTrace.completeSpanOffset =
1253-
transition.completeSpanAndAnnotation.annotation.operationRelativeEndTime
1254-
}
1253+
if (
1254+
'completeSpanAndAnnotation' in transition &&
1255+
transition.completeSpanAndAnnotation
1256+
) {
1257+
updatedTrace.completeSpanOffset =
1258+
transition.completeSpanAndAnnotation.annotation.operationRelativeEndTime
1259+
}
12551260

1256-
if (
1257-
'cpuIdleSpanAndAnnotation' in transition &&
1258-
transition.cpuIdleSpanAndAnnotation
1259-
) {
1260-
updatedTrace.cpuIdleSpanOffset =
1261-
transition.cpuIdleSpanAndAnnotation.annotation.operationRelativeEndTime
1262-
}
1261+
if (
1262+
'cpuIdleSpanAndAnnotation' in transition &&
1263+
transition.cpuIdleSpanAndAnnotation
1264+
) {
1265+
updatedTrace.cpuIdleSpanOffset =
1266+
transition.cpuIdleSpanAndAnnotation.annotation.operationRelativeEndTime
1267+
}
12631268

1264-
// Terminal states - add to history
1265-
if (isTerminalState(transition.transitionToState)) {
1266-
setTraceHistory((prev) => {
1267-
updatedTrace.finalTransition =
1268-
transition as FinalTransition<RelationSchemasT>
1269-
const newHistory = [updatedTrace, ...prev]
1270-
return newHistory.slice(0, traceHistoryLimit)
1271-
})
1272-
return null
1273-
}
1269+
// Terminal states - add to history
1270+
if (isTerminalState(transition.transitionToState)) {
1271+
setTraceHistory((prev) => {
1272+
updatedTrace.finalTransition =
1273+
transition as FinalTransition<RelationSchemasT>
1274+
const newHistory = [updatedTrace, ...prev]
1275+
return newHistory.slice(0, traceHistoryLimit)
1276+
})
1277+
return null
1278+
}
12741279

1275-
return updatedTrace
1276-
})
1280+
return updatedTrace
1281+
}),
1282+
)
12771283
})
12781284

12791285
// Subscribe to required span seen events
@@ -1282,28 +1288,31 @@ export default function TraceManagerDebugger<
12821288
.subscribe((event) => {
12831289
const trace = event.traceContext as AllPossibleTraces<RelationSchemasT>
12841290

1285-
setCurrentTrace((prevTrace) => {
1286-
if (!prevTrace || prevTrace.traceId !== trace.input.id) {
1287-
return prevTrace
1288-
}
1289-
// Find which required span was matched by comparing against all matchers
1290-
const updatedRequiredSpans = [...prevTrace.requiredSpans]
1291-
const matchedSpan = event.spanAndAnnotation
1292-
1293-
trace.definition.requiredSpans.forEach((matcher, index) => {
1294-
if (matcher(matchedSpan, trace)) {
1295-
updatedRequiredSpans[index] = {
1296-
...updatedRequiredSpans[index]!,
1297-
isMatched: true,
1291+
schedule(
1292+
() =>
1293+
void setCurrentTrace((prevTrace) => {
1294+
if (!prevTrace || prevTrace.traceId !== trace.input.id) {
1295+
return prevTrace
12981296
}
1299-
}
1300-
})
1301-
1302-
return {
1303-
...prevTrace,
1304-
requiredSpans: updatedRequiredSpans,
1305-
}
1306-
})
1297+
// Find which required span was matched by comparing against all matchers
1298+
const updatedRequiredSpans = [...prevTrace.requiredSpans]
1299+
const matchedSpan = event.spanAndAnnotation
1300+
1301+
trace.definition.requiredSpans.forEach((matcher, index) => {
1302+
if (matcher(matchedSpan, trace)) {
1303+
updatedRequiredSpans[index] = {
1304+
...updatedRequiredSpans[index]!,
1305+
isMatched: true,
1306+
}
1307+
}
1308+
})
1309+
1310+
return {
1311+
...prevTrace,
1312+
requiredSpans: updatedRequiredSpans,
1313+
}
1314+
}),
1315+
)
13071316
})
13081317

13091318
const entries: SpanAndAnnotation<RelationSchemasT>[] = []
@@ -1312,58 +1321,65 @@ export default function TraceManagerDebugger<
13121321
const addSpanSub = traceManager
13131322
.when('add-span-to-recording')
13141323
.subscribe((event) => {
1315-
setCurrentTrace((prevTrace) => {
1316-
if (!prevTrace) return prevTrace
1317-
if (event.traceContext.input.id !== prevTrace.traceId) {
1318-
return prevTrace
1319-
}
1320-
// Calculate live info from traceContext
1321-
const trace = event.traceContext
1322-
entries.push(event.spanAndAnnotation)
1323-
1324-
const liveDuration =
1325-
entries.length > 0
1326-
? Math.round(
1327-
Math.max(
1328-
...entries.map(
1329-
(e) => e.span.startTime.epoch + e.span.duration,
1330-
),
1331-
) - trace.input.startTime.epoch,
1332-
)
1333-
: 0
1334-
const totalSpanCount = entries.length
1335-
const hasErrorSpan = entries.some(
1336-
(e) => e.span.status === 'error' && !isSuppressedError(trace, e),
1337-
)
1338-
const hasSuppressedErrorSpan = entries.some(
1339-
(e) => e.span.status === 'error' && isSuppressedError(trace, e),
1340-
)
1341-
return {
1342-
...prevTrace,
1343-
liveDuration,
1344-
totalSpanCount,
1345-
hasErrorSpan,
1346-
hasSuppressedErrorSpan,
1347-
}
1348-
})
1324+
schedule(
1325+
() =>
1326+
void setCurrentTrace((prevTrace) => {
1327+
if (!prevTrace) return prevTrace
1328+
if (event.traceContext.input.id !== prevTrace.traceId) {
1329+
return prevTrace
1330+
}
1331+
// Calculate live info from traceContext
1332+
const trace = event.traceContext
1333+
entries.push(event.spanAndAnnotation)
1334+
1335+
const liveDuration =
1336+
entries.length > 0
1337+
? Math.round(
1338+
Math.max(
1339+
...entries.map(
1340+
(e) => e.span.startTime.epoch + e.span.duration,
1341+
),
1342+
) - trace.input.startTime.epoch,
1343+
)
1344+
: 0
1345+
const totalSpanCount = entries.length
1346+
const hasErrorSpan = entries.some(
1347+
(e) =>
1348+
e.span.status === 'error' && !isSuppressedError(trace, e),
1349+
)
1350+
const hasSuppressedErrorSpan = entries.some(
1351+
(e) => e.span.status === 'error' && isSuppressedError(trace, e),
1352+
)
1353+
return {
1354+
...prevTrace,
1355+
liveDuration,
1356+
totalSpanCount,
1357+
hasErrorSpan,
1358+
hasSuppressedErrorSpan,
1359+
}
1360+
}),
1361+
)
13491362
})
13501363

13511364
// Subscribe to definition-modified for modification indicator
13521365
const defModSub = traceManager
13531366
.when('definition-modified')
13541367
.subscribe((event) => {
1355-
setCurrentTrace((prevTrace) => {
1356-
if (!prevTrace) return prevTrace
1357-
if (event.traceContext.input.id !== prevTrace.traceId)
1358-
return prevTrace
1359-
return {
1360-
...prevTrace,
1361-
definitionModifications: [
1362-
...(prevTrace.definitionModifications ?? []),
1363-
event.modifications,
1364-
],
1365-
}
1366-
})
1368+
schedule(
1369+
() =>
1370+
void setCurrentTrace((prevTrace) => {
1371+
if (!prevTrace) return prevTrace
1372+
if (event.traceContext.input.id !== prevTrace.traceId)
1373+
return prevTrace
1374+
return {
1375+
...prevTrace,
1376+
definitionModifications: [
1377+
...(prevTrace.definitionModifications ?? []),
1378+
event.modifications,
1379+
],
1380+
}
1381+
}),
1382+
)
13671383
})
13681384

13691385
return () => {

0 commit comments

Comments
 (0)