React
Render React into real Gtk and Adw widgets. There is no compile plugin and no DOM, and
react-dom is not involved. React’s JSX is already a runtime, so two lines of tsconfig.json
and two build flags are the whole setup.
You keep React. Components, hooks, context, error boundaries, useState, useEffect and
Suspense behave the way they do anywhere else. What changes is the element vocabulary:
<gtk-box> instead of <div>.
Install
Section titled “Install”npm install -D @gjsify/gtk-host react react-reconciler @types/reactyarn add -D @gjsify/gtk-host react react-reconciler @types/reactpnpm add -D @gjsify/gtk-host react react-reconciler @types/reactbun add --dev @gjsify/gtk-host react react-reconciler @types/reactdeno add --dev npm:@gjsify/gtk-host npm:react npm:react-reconciler npm:@types/reactgjsify install @gjsify/gtk-host react react-reconciler @types/reactInstalling react and react-reconciler is what selects the React adapter. They are optional
peers, so nothing enforces a version on install.
Pin React 19. The adapter needs react ^19.2.0 and react-reconciler ^0.33.0. On a React 18
reconciler the synchronous render path is not there to call: flushSync was renamed
flushSyncFromReconciler, updateContainerSync and flushSyncWork arrived with React 19, and
createContainer grew two arguments in the middle of its list. None of that is a type error.
Configure TypeScript
Section titled “Configure TypeScript”{ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@gjsify/gtk-host/react", "noImplicitAny": true }}jsxImportSource names the subpath, not react. Write "jsxImportSource": "react" and the
element list comes from @types/react, so <div> type-checks in a program that cannot draw one.
With the subpath, <div/> is a TS2339 naming the element and <gtk-label label="hi" /> is
checked against the real GtkLabel properties.
Render a tree
Section titled “Render a tree”import { createRoot } from '@gjsify/gtk-host/react';
const root = createRoot(myWindow);root.render( <gtk-box orientation="vertical" spacing={12}> <gtk-label label="hi" /> </gtk-box>,);createRoot takes a widget your application already built and records what that container
already held, so your tree renders after the app’s own chrome instead of replacing it.
render() commits before it returns, so the widgets are on screen when the call comes back. It
also rethrows the first uncaught error at the call site, so a broken tree is a stack trace rather
than an empty window.
Build it
Section titled “Build it”gjsify build src/app.tsx --app gjs --outfile dist/app.gjs.mjs \ --define 'process.env.NODE_ENV="production"' \ --exclude-globals navigatorBoth flags are required. Leave either out and the bundle builds, then drags in GTK drawing libraries it never uses.
--define picks React’s production build. Without it you bundle the development reconciler,
which reaches for document, HTMLCanvasElement and Path2D; the global scanner answers those
by pulling gi://Gdk, GdkPixbuf, Pango and PangoCairo into the bundle.
--exclude-globals navigator covers the scheduler, which reads navigator.scheduling behind a
typeof guard. The branch is dead under GJS, but the identifier is still free and the scanner
answers it the same way.
Vue needs the same NODE_ENV define and three Vue-specific ones besides, and it needs no
navigator flag. The Vue page has that recipe in full.
Update outside a render
Section titled “Update outside a render”A setState from a GTK signal handler lands on a later main-loop iteration, which is what you
want in a running application. When you need the change on screen immediately, usually in a test
with no main loop, flush it:
import { flushSync } from '@gjsify/gtk-host/react';
flushSync(() => setCount(1));// the label's text has already changed hereWhat the adapter exports
Section titled “What the adapter exports”| Export | What it is |
|---|---|
createRoot(container, options?) | a root over a widget you own: render, unmount, container |
mount(element, container, options?) | createRoot(…).render(…) in one call |
flushSync(fn) | run fn and flush every update it schedules |
widgetOf(node) | the Gtk.Widget behind a host node |
adopt(widget) | wrap a container you hold in a host element |
gtkHostConfig | the raw react-reconciler HostConfig, for reconciler-level control |
Handle errors
Section titled “Handle errors”createRoot takes React 19’s three error callbacks:
| Option | Fires for | Default |
|---|---|---|
onUncaughtError | an error no boundary caught, so the tree is broken | logs, and render() rethrows |
onCaughtError | an error a boundary did catch | console.error |
onRecoverableError | an error React recovered from and retried | console.error |
const root = createRoot(myWindow, { onUncaughtError(error) { reportToYourOwnSurface(error); },});Passing onUncaughtError turns the rethrow off. That is the right trade for an application
with its own error surface, but you then own reporting entirely. Pass it to take over reporting,
not to add a log line.
Three differences from react-dom
Section titled “Three differences from react-dom”children, ref and key never reach GObject. The adapter reserves children and ref,
and React keeps key out of props on its own. Everything else in props is set on the widget,
so a typo reads <GtkBox> has no property "colour" instead of doing nothing.
ref gives you your own widget. Where the host wraps a child, a GtkListBoxRow around a row
for instance, the ref still hands back the widget you wrote.
import { useEffect, useRef } from 'react';import type Gtk from '@girs/gtk-4.0';
function SearchField() { const entry = useRef<Gtk.Entry>(null);
useEffect(() => entry.current?.grab_focus(), []);
return <gtk-entry ref={entry} placeholder-text="Search" />;}The first commit does not clear your window. React clears a root before its first commit. Here that reaches your tree only and never touches chrome your application put there.
Related
Section titled “Related”- Getting started if you do not have a gjsify project yet
- UI Frameworks for the host, the widget table and the placement rules
- React Native if your components are written with
ViewandTextrather than<gtk-box> - Styling on GTK for class names and token scales
react-host-counterfor a complete window that asserts its own widget tree on every launch- How the React layers work for the measurement behind each rule above, if you are changing this part of gjsify