some trouble with state in TypeScript React.
A Child component passes a ’terminal’ object to the Parent through a passed function named returnTerminal(). This terminal object is then stored as a useState _object. useEffect() says that _object is not null eventually, but callback() continuously maintains that _object is null, and I’m not sure why.
Parent Component
const Parent: React.FunctionComponent<ParentProps> = () => {
const [_object, set_object] = useState<Terminal>(null);
const handleGetTerminal = (terminal: Terminal) => {
set_object(terminal);
};
useEffect(() => {
if (_object !== null) {
_object.writeln("_object is not null"); // This prints just fine
}
}, [_object]);
return (
<Child
returnTerminal={(term) => handleGetTerminal(term)}
callback={() => {
console.log(_object === null); // This returns true - the object is null for some reason???
}}
/>
);
};
Child Component
const Child: React.FunctionComponent<ChildProps> = (props) => {
const { callback, returnTerminal } = props;
// The ref where the terminal will be stored and returned
const ref = useRef<HTMLDivElement>(null);
// The xterm.js terminal object
const terminal = new Terminal();
useLayoutEffect(() => {
// Calls whenever terminal is typed into
if (callback) terminal.onData(callback);
// Mount terminal to the DOM
if (ref.current) terminal.open(ref.current);
// Pass the terminal to the parent object
returnTerminal(terminal);
}, []);
return <div ref={ref}></div>;
};
export default Child;
The callback() always returns that _object is null, no matter how long I wait.
The answer ended up being the use of useImperativeHandle and forwardRef. Here’s the completed code.
Parent
import Child from "./child";
interface ParentProps {
name?: string;
}
const Parent: React.FunctionComponent<ParentProps> = () => {
type ChildHandle = ElementRef<typeof Child>;
const childRef = useRef<Child>(null);
let _object: Terminal; // same Terminal type that Child uses
useEffect(() => {
if (childRef.current) {
_object = childRef.current.get(); // imperitive handle function
_object.writeln(“_object loaded”);
}
}, []);
return (
<Child
ref={childRef}
callback={() => {
terminal.writeln("_object is not null”); // THIS finally works
}}
/>
);
};
Child
interface ChildProps {
callback?(data: string): void;
}
type ChildHandle = {
get: () => Terminal;
};
const Child: ForwardRefRenderFunction<ChildHandle, ChildProps> = (
props,
forRef
) => {
const { callback } = props;
const terminal = new Terminal();
// The ref where the terminal will be stored and returned
const ref = useRef<HTMLDivElement>(null);
useImperativeHandle(forRef, () => ({
get() {
return terminal;
},
}));
useLayoutEffect(() => {
// Calls whenever terminal is typed into
if (callback) terminal.onData(callback);
// Mount terminal to the DOM
if (ref.current) terminal.open(ref.current);
}, []);
return <div ref={ref}></div>;
};
export default forwardRef(Child);