EDIT: More information
useEffect(() => {
// let terminal;
if (terminalRef.current) {
const terminal = new Terminal({
fontFamily: "Menlo, Monaco, monospace",
// fontSize: 12,
// cursorBlink: true,
theme: {
background: "#001e4821",
foreground: "#f0f0f0",
},
});
terminal.open(terminalRef.current);
setTerm(terminal);
}
// return () => {
// terminal.dispose();
// };
}, []);
initializing Terminal after the ref is defined will remove the error. However, I can no longer return a cleanup function (because terminal is instantiated inside the if statement)
useEffect(() => {
let terminal; // Declare variable in the useEffect scope but outside the if block
if (terminalRef.current) {
terminal = new Terminal({
// Instantiate the terminal inside the if block
fontFamily: "Menlo, Monaco, monospace",
theme: {
background: "#001e4821",
foreground: "#f0f0f0",
},
});
terminal.open(terminalRef.current);
setTerm(terminal);
}
return () => {
// Cleanup function
if (terminal) {
terminal.dispose(); // Dispose of the terminal instance if it has been created
}
};
}, []);
This doesn't work either. I need to return the cleanup function, otherwise it renders two terminals.
This is the error:
Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading 'dimensions')
Call Stack
get dimensions
node_modules/xterm/lib/xterm.js (1:103398)
t.Viewport._innerRefresh
node_modules/xterm/lib/xterm.js (1:49940)
eval
node_modules/xterm/lib/xterm.js (1:49741)
this is the code:
"use client";
import React, { useRef, useEffect, useState, useMemo } from "react";
import { Terminal } from "xterm";
import "xterm/css/xterm.css";
const XTerminal: React.FC = () => {
const terminalRef = useRef<HTMLDivElement | null>(null);
const [term, setTerm] = useState<Terminal | null>(null);
useEffect(() => {
const terminal = new Terminal({
fontFamily: "Menlo, Monaco, monospace",
fontSize: 12,
cursorBlink: true,
theme: {
background: "#001e4821",
foreground: "#f0f0f0",
},
});
if (terminalRef.current) {
terminal.open(terminalRef.current);
setTerm(terminal);
}
return () => {
terminal.dispose();
};
}, []);
useEffect(() => {
if (term) {
//more code
}
}, [term]);
return <div ref={terminalRef}></div>;
};
export default XTerminal;
This is the parent component:
<div className="z-30 px-8 absolute w-100 h-100 left-2 top-1/4">
<XTerminal />
</div>
I'm using Nextjs14 and using xtermjs v5.3. This is code I wrote in September (using v5.2) that is no longer working. I tried downgrading to v5.2 but the error still appears.
I'm not sure what the issue is.
I thought it might have to do with width/height not being explicitly specified or absolute positioning, but its not the case. I am rendering client side ('use client') so I'm assuming that's not the issue either.
There is no "dimensions" property in the constructor. https://xtermjs.org/docs/api/terminal/classes/terminal/#constructor I noticed a "windowOptions" property but changing these makes no difference to the error. https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/#optional-windowoptions
Any ideas/solutions? Please let me know if you need further clarification.
Edit- You will also need to import this dynamically with no ssr. in nextjs 14 the code is as follows:
const XTerminalNoSSR = dynamic(() => import("./terminal/terminal"), {
ssr: false, // This disables server-side rendering for the import
});
useEffect(() => {
const terminal = new Terminal({
fontFamily: "Menlo, Monaco, monospace",
theme: {
background: "#001e4821",
foreground: "#f0f0f0",
},
});
setTerm(terminal);
return () => {
if (terminal) {
terminal.dispose();
}
};
}, []);
useEffect(() => {
if (term) {
if (terminalRef.current) {
term.open(terminalRef.current);
setTerm(term);
term?.write(`hello`);
}
const disposable = term.onData((data) => {
if (data === "\r") {
term?.write(`\r\n`);
} else if (data.charCodeAt(0) === 127) {
term?.write("\b \b");
} else {
term?.write(data);
}
});
return () => {
disposable.dispose();
};
}
}, [term]);
The error seems to indicate that the terminal is being initialized before the ref's dimensions are communicated to it. the offending line of code is 'term.open()'
Therefore, I initialized the terminal and set it on first mount, Then upon 'term' change, conditionally opened the terminal based on the existence of its ref.
I don't know why this works. Probably has to do with the order of the render cycle.