@@ -80,6 +80,11 @@ export const XTerm: React.FunctionComponent<XTermProps> = ({
8080 } , [ onBeforeUnload ] ) ;
8181
8282 useEffect ( ( ) => {
83+ const container = ref . current ;
84+ if ( ! container ) {
85+ return ;
86+ }
87+
8388 const fitAddon = new FitAddon ( ) ;
8489 terminalRef . current = new Terminal ( {
8590 cols,
@@ -92,8 +97,8 @@ export const XTerm: React.FunctionComponent<XTermProps> = ({
9297
9398 const onWindowResize = ( ) => {
9499 const geometry = fitAddon . proposeDimensions ( ) ;
95- if ( geometry ) {
96- terminalRef . current . resize ( geometry . rows , geometry . cols ) ;
100+ if ( geometry && terminalRef . current ) {
101+ terminalRef . current . resize ( geometry . cols , geometry . rows ) ;
97102 }
98103 } ;
99104
@@ -107,29 +112,45 @@ export const XTerm: React.FunctionComponent<XTermProps> = ({
107112
108113 terminalRef . current . loadAddon ( fitAddon ) ;
109114
110- terminalRef . current . open ( ref . current ) ;
115+ let resizeListener : ( ( ) => void ) | null = null ;
111116
112- const resizeListener = debounce ( onWindowResize , 100 ) ;
113-
114- if ( ! rows ) {
115- if ( canUseDOM ) {
117+ // Defer open so the container is laid out and xterm's renderer has valid dimensions
118+ // before Viewport._innerRefresh runs (avoids "Cannot read properties of undefined (reading 'dimensions')")
119+ const rafId = requestAnimationFrame ( ( ) => {
120+ if ( ! container . isConnected || ! terminalRef . current ) {
121+ return ;
122+ }
123+ terminalRef . current . open ( container ) ;
124+ resizeListener = ! rows ? debounce ( onWindowResize , 100 ) : null ;
125+ if ( resizeListener && canUseDOM ) {
116126 window . addEventListener ( 'resize' , resizeListener ) ;
117127 }
118- onWindowResize ( ) ;
119- }
120- terminalRef . current . focus ( ) ;
128+ if ( ! rows ) {
129+ onWindowResize ( ) ;
130+ }
131+ terminalRef . current ?. focus ( ) ;
132+ } ) ;
121133
122134 return ( ) => {
123- terminalRef . current . dispose ( ) ;
124- if ( canUseDOM ) {
135+ cancelAnimationFrame ( rafId ) ;
136+ if ( resizeListener && canUseDOM ) {
125137 window . removeEventListener ( 'resize' , resizeListener ) ;
126138 }
139+ terminalRef . current ?. dispose ( ) ;
140+ terminalRef . current = null ;
127141 onFocusOut ( ) ;
128142 } ;
129143 } , [ cols , fontFamily , fontSize , onData , onFocusOut , onTitleChanged , rows ] ) ;
130144
131145 // ensure react never reuses this div by keying it with the terminal widget
132146 // Workaround for xtermjs/xterm.js#3172
133- return < div ref = { ref } role = "list" onFocus = { onFocusIn } onBlur = { onFocusOut } /> ;
147+ return (
148+ < div
149+ ref = { ref }
150+ role = "list"
151+ onFocus = { onFocusIn }
152+ onBlur = { onFocusOut }
153+ />
154+ ) ;
134155} ;
135156XTerm . displayName = 'XTerm' ;
0 commit comments