# React Gotchas ## Critical ### Never use `process.exit()` directly **This is the most common mistake.** Using `process.exit()` leaves the terminal in a broken state (cursor hidden, raw mode, alternate screen). ```tsx // WRONG - Terminal left in broken state process.exit(0) // CORRECT - Use renderer.destroy() import { useRenderer } from "@opentui/react" function App() { const renderer = useRenderer() const handleExit = () => { renderer.destroy() // Cleans up and exits properly } } ``` `renderer.destroy()` restores the terminal (exits alternate screen, restores cursor, etc.) before exiting. ## JSX Configuration ### Missing jsxImportSource **Symptom**: JSX elements have wrong types, components don't render ``` // Error: Property 'text' does not exist on type 'JSX.IntrinsicElements' ``` **Fix**: Configure tsconfig.json: ```json { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@opentui/react" } } ``` ### HTML Elements vs TUI Elements OpenTUI's JSX elements are **not** HTML elements: ```tsx // WRONG - These are HTML concepts
Not supported
Only works inside // CORRECT - OpenTUI elements Container Display text Inline styled ``` ## Component Issues ### Text Modifiers Outside Text Text modifiers only work inside ``: ```tsx // WRONG This won't work // CORRECT This works ``` ### Focus Not Working Components must be explicitly focused: ```tsx // WRONG - Won't receive keyboard input // CORRECT // Or manage focus state const [isFocused, setIsFocused] = useState(true) ``` ### Select Not Responding Select requires focus and proper options format: ```tsx // WRONG - Missing required properties { // Called when Enter is pressed console.log("Selected:", option.name) }} focused /> ``` ### Select Events Confusion Remember: `onSelect` fires on Enter (selection confirmed), `onChange` fires on navigation: ```tsx // WRONG - expecting onChange to fire on Enter submitForm(opt)} // Enter pressed - submit onChange={(i, opt) => showPreview(opt)} // Arrow keys - preview /> ``` ## Hook Issues ### useKeyboard Not Firing Multiple `useKeyboard` hooks can conflict: ```tsx // Both handlers fire - may cause issues function App() { useKeyboard((key) => { /* parent handler */ }) return } function ChildWithKeyboard() { useKeyboard((key) => { /* child handler */ }) return Child } ``` **Solution**: Use a single keyboard handler or implement event stopping: ```tsx function App() { const [handled, setHandled] = useState(false) useKeyboard((key) => { if (handled) { setHandled(false) return } // Handle at app level }) return setHandled(true)} /> } ``` ### useEffect Cleanup Always clean up intervals and listeners: ```tsx // WRONG - Memory leak useEffect(() => { setInterval(() => updateData(), 1000) }, []) // CORRECT useEffect(() => { const interval = setInterval(() => updateData(), 1000) return () => clearInterval(interval) // Cleanup! }, []) ``` ## Styling Issues ### Colors Not Applying Check color format: ```tsx // CORRECT formats Red Red Box // WRONG Missing # Wrong prop name (use fg) ``` ### Layout Not Working Ensure parent has dimensions: ```tsx // WRONG - Parent has no height Won't grow // CORRECT Will grow ``` ### Percentage Widths Not Working Parent must have explicit dimensions: ```tsx // WRONG Won't work // CORRECT Works ``` ## Performance Issues ### Too Many Re-renders Avoid inline objects/functions in props: ```tsx // WRONG - New object every render Content // BETTER - Use direct props Content // OR memoize style objects const style = useMemo(() => ({ padding: 2 }), []) Content ``` ### Heavy Components Use React.memo for expensive components: ```tsx const ExpensiveList = React.memo(function ExpensiveList({ items }: { items: Item[] }) { return ( {items.map(item => ( {item.name} ))} ) }) ``` ### State Updates During Render Don't update state during render: ```tsx // WRONG function Component({ value }: { value: number }) { const [count, setCount] = useState(0) // This causes infinite loop! if (value > 10) { setCount(value) } return {count} } // CORRECT function Component({ value }: { value: number }) { const [count, setCount] = useState(0) useEffect(() => { if (value > 10) { setCount(value) } }, [value]) return {count} } ``` ## Debugging ### Console Not Visible OpenTUI captures console output. Show the overlay: ```tsx import { useRenderer } from "@opentui/react" import { useEffect } from "react" function App() { const renderer = useRenderer() useEffect(() => { renderer.console.show() console.log("Now you can see this!") }, [renderer]) return {/* ... */} } ``` ### Component Not Rendering Check if component is in the tree: ```tsx // WRONG - Conditional returns nothing function MaybeComponent({ show }: { show: boolean }) { if (!show) return // Returns undefined! return Visible } // CORRECT function MaybeComponent({ show }: { show: boolean }) { if (!show) return null // Explicit null return Visible } ``` ### Events Not Firing Check event handler names: ```tsx // WRONG {}}>Click // No onClick in TUI // CORRECT {}}>Click {}}>Click ``` ## Runtime Issues ### Use Bun, Not Node ```bash # WRONG node src/index.tsx npm run start # CORRECT bun run src/index.tsx bun run start ``` ### Async Top-level Bun supports top-level await, but be careful: ```tsx // index.tsx - This works in Bun const renderer = await createCliRenderer() createRoot(renderer).render() // If you need to handle errors try { const renderer = await createCliRenderer() createRoot(renderer).render() } catch (error) { console.error("Failed to initialize:", error) process.exit(1) } ``` ## Common Error Messages ### "Cannot read properties of undefined (reading 'root')" Renderer not initialized: ```tsx // WRONG const renderer = createCliRenderer() // Missing await! createRoot(renderer).render() // CORRECT const renderer = await createCliRenderer() createRoot(renderer).render() ``` ### "Invalid hook call" Hooks called outside component: ```tsx // WRONG const dimensions = useTerminalDimensions() // Outside component! function App() { return {dimensions.width} } // CORRECT function App() { const dimensions = useTerminalDimensions() return {dimensions.width} } ```