@@ -37,21 +37,20 @@ export const GameStream: React.FC<GameStreamProps> = (
37
37
} ) ;
38
38
39
39
const [ gameLogLines , setGameLogLines ] = useState < Array < GameLogLine > > ( [ ] ) ;
40
+ const clearStreamTimeoutRef = useRef < NodeJS . Timeout > ( ) ;
40
41
41
- const appendGameLogLine = useCallback ( ( newLogLine : GameLogLine ) => {
42
+ const appendGameLogLines = useCallback ( ( newLogLines : Array < GameLogLine > ) => {
42
43
// Max number of most recent lines to keep.
43
44
const scrollbackBuffer = 500 ;
44
45
setGameLogLines ( ( oldLogLines ) => {
45
46
// Append new log line to the list.
46
- let newLogLines = oldLogLines . concat ( newLogLine ) ;
47
+ newLogLines = oldLogLines . concat ( newLogLines ) ;
47
48
// Trim the back of the list to keep it within the scrollback buffer.
48
49
newLogLines = newLogLines . slice ( scrollbackBuffer * - 1 ) ;
49
50
return newLogLines ;
50
51
} ) ;
51
52
} , [ ] ) ;
52
53
53
- const clearStreamTimeoutRef = useRef < NodeJS . Timeout > ( ) ;
54
-
55
54
// Ensure all timeouts are cleared when the component is unmounted.
56
55
useEffect ( ( ) => {
57
56
return ( ) => {
@@ -60,23 +59,30 @@ export const GameStream: React.FC<GameStreamProps> = (
60
59
} , [ ] ) ;
61
60
62
61
useSubscription ( filteredStream$ , ( logLine ) => {
63
- if ( logLine . text === '__CLEAR_STREAM__' ) {
64
- // Clear the stream after a short delay to prevent flickering
65
- // caused by a flash of empty content then the new content.
66
- clearStreamTimeoutRef . current = setTimeout ( ( ) => {
67
- setGameLogLines ( [ ] ) ;
68
- } , 1000 ) ;
69
- } else {
70
- // If we receieved a new log line, cancel any pending clear stream.
71
- // Set the game log lines to the new log line to prevent flickering.
72
- if ( clearStreamTimeoutRef . current ) {
73
- clearTimeout ( clearStreamTimeoutRef . current ) ;
74
- clearStreamTimeoutRef . current = undefined ;
75
- setGameLogLines ( [ logLine ] ) ;
62
+ // Decouple state updates from the stream subscription to mitigate
63
+ // "Cannot update a component while rendering a different component".
64
+ // This gives some control of the event loop back to react
65
+ // to smartly (re)render all components and state changes.
66
+ // We use `setTimeout` because browser doesn't have `setImmediate`.
67
+ setTimeout ( ( ) => {
68
+ if ( logLine . text === '__CLEAR_STREAM__' ) {
69
+ // Clear the stream after a short delay to prevent flickering
70
+ // caused by a flash of empty content then the new content.
71
+ clearStreamTimeoutRef . current = setTimeout ( ( ) => {
72
+ setGameLogLines ( [ ] ) ;
73
+ } , 1000 ) ;
76
74
} else {
77
- appendGameLogLine ( logLine ) ;
75
+ // If we receieved a new log line, cancel any pending clear stream.
76
+ // Set the game log lines to the new log line to prevent flickering.
77
+ if ( clearStreamTimeoutRef . current ) {
78
+ clearTimeout ( clearStreamTimeoutRef . current ) ;
79
+ clearStreamTimeoutRef . current = undefined ;
80
+ setGameLogLines ( [ logLine ] ) ;
81
+ } else {
82
+ appendGameLogLines ( [ logLine ] ) ;
83
+ }
78
84
}
79
- }
85
+ } ) ;
80
86
} ) ;
81
87
82
88
// Scroll to the bottom of the scrollable element when new content is added.
0 commit comments