How to Integrate React for the Frontend of a Tauri Application: A Complete Guide
Integrate React into a Tauri application by mounting a React tree inside the WebView, using Tauri’s JavaScript APIs to communicate with the Rust backend, and routing multiple UI views via webview labels as demonstrated in the lencx/ChatGPT repository.
The lencx/ChatGPT project provides a production-ready blueprint for building desktop applications with React and Tauri. By following the patterns established in this repository, you can integrate React for the frontend of a Tauri application while maintaining seamless bidirectional communication between the JavaScript frontend and the Rust backend.
Bootstrap the React Entry Point
Every Tauri application needs a webview entry point that mounts your React application. In the ChatGPT repository, src/main.tsx serves as the bootstrap file that renders the root component into the DOM.
Create a standard React 18+ mounting pattern inside your Tauri window:
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './base.css';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<StrictMode>
<App />
</StrictMode>
);
The Tauri WebView loads this JavaScript bundle and executes the mounting logic when the native window initializes. This file acts as the bridge between the static HTML container and the dynamic React frontend.
Route Views Based on Webview Labels
Tauri supports multiple webviews within a single application window, each identified by a unique label. The ChatGPT repository leverages this feature to render distinct React components without a traditional client-side router.
In src/App.tsx, the application reads the current webview label and selects the appropriate view component:
import { getCurrentWebview } from '@tauri-apps/api/webview';
import Titlebar from '~view/Titlebar';
import Ask from '~view/Ask';
import Settings from '~view/Settings';
const viewMap = {
titlebar: <Titlebar />,
ask: <Ask />,
settings: <Settings />,
};
export default function App() {
const webview = getCurrentWebview();
return viewMap[webview.label as keyof typeof viewMap] || null;
}
This pattern allows you to host separate UI sections—such as a custom title bar, chat interface, and settings panel—within isolated webviews while sharing the same React build. The getCurrentWebview() function from @tauri-apps/api/webview returns the label assigned in your Tauri configuration, enabling conditional rendering logic.
Invoke Native Commands from React Components
The core of React-Tauri integration lies in invoking Rust commands from React event handlers. The repository demonstrates this extensively in src/view/Titlebar.tsx, where UI interactions trigger native window controls and system actions.
Import the invoke function from @tauri-apps/api/core to call Rust backend functions:
import { invoke } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { open } from '@tauri-apps/plugin-shell';
// Example command invocations
const handleRefresh = () => invoke('view_reload');
const handleTheme = (theme: string) => invoke('set_theme', { theme });
const handleOpenUrl = (url: string) => open(url);
Each invoke call maps to a corresponding Rust command defined in src-tauri/src/core/cmd.rs. For example, a counter component might invoke a Rust function to persist state:
import { useState } from 'react';
import { invoke } from '@tauri-apps/api/core';
export default function Counter() {
const [count, setCount] = useState(0);
const inc = async () => {
const newCount = await invoke<number>('increment_counter', { value: count });
setCount(newCount);
};
return (
<button onClick={inc} className="p-2 bg-blue-500 text-white">
Clicked {count} times
</button>
);
}
The Rust implementation receives the payload and returns data synchronously or asynchronously:
#[tauri::command]
fn increment_counter(value: i32) -> i32 {
value + 1
}
Listen to Tauri Window Events
React components can subscribe to native window events using Tauri’s event system. This enables real-time UI updates when the window state changes, such as entering fullscreen or resizing.
The following pattern from the repository demonstrates listening to resize events in src/view/Titlebar.tsx:
import { useEffect, useState } from 'react';
import { getCurrentWindow } from '@tauri-apps/api/window';
export default function FullscreenToggle() {
const [isFull, setIsFull] = useState(false);
const win = getCurrentWindow();
useEffect(() => {
win.isFullscreen().then(setIsFull);
const unlisten = win.listen('tauri://resize', async () => {
const full = await win.isFullscreen();
setIsFull(full);
});
return () => unlisten.then((f) => f());
}, []);
const toggle = async () => {
const full = await win.isFullscreen();
await win.setFullscreen(!full);
setIsFull(!full);
};
return (
<button onClick={toggle}>
{isFull ? 'Exit Fullscreen' : 'Enter Fullscreen'}
</button>
);
}
The listen method returns an unlisten function wrapped in a Promise. Always clean up subscriptions in the effect cleanup phase to prevent memory leaks when components unmount.
Wire Up the Rust Backend
To complete the integration, define your command handlers in the Tauri Rust codebase. The ChatGPT repository centralizes these in src-tauri/src/core/cmd.rs.
Expose functions using the #[tauri::command] macro and register them in your Tauri builder:
#[tauri::command]
fn view_reload() {
// Implementation for reloading the view
}
#[tauri::command]
fn set_theme(theme: String) {
// Implementation for setting the application theme
}
These commands become available to the React frontend immediately after the Tauri application initializes, creating a type-safe bridge between the frontend and native capabilities.
Configure the Build Pipeline
The Vite configuration in vite.config.ts bundles the React application and outputs static assets that Tauri packages into the native binary. Ensure your build directory points to a location accessible by the Tauri bundler, typically within the src-tauri directory or a designated dist folder.
Development workflow uses the standard Tauri CLI:
pnpm install
pnpm tauri dev
This command starts the Vite development server with Hot Module Replacement (HMR) and launches the Tauri WebView pointed at the dev server. For production, pnpm tauri build compiles the React assets and Rust binary into distributable installers.
Summary
- Mount React in the WebView using a standard React 18 root in
src/main.tsxto initialize your frontend inside the Tauri window. - Route by webview labels by calling
getCurrentWebview()insrc/App.tsxto render different components for separate native windows or views. - Invoke Rust commands from React using
invoke()from@tauri-apps/api/core, mapping UI events to backend logic defined insrc-tauri/src/core/cmd.rs. - Subscribe to native events with
window.listen()to update React state based on window resizing, fullscreen changes, or custom Tauri events. - Build with Vite to bundle React code that Tauri embeds, using
tauri devfor development andtauri buildfor production releases.
Frequently Asked Questions
How do I pass data from React to the Rust backend in a Tauri application?
Use the invoke function from @tauri-apps/api/core with typed arguments. The Rust command handler receives these as function parameters. For example, invoke('set_theme', { theme: 'dark' }) passes a serializable object to the Rust side where #[tauri::command] fn set_theme(theme: String) handles the value.
Can I use React Router inside a Tauri WebView?
Yes, but the ChatGPT repository demonstrates an alternative pattern using webview labels. Since Tauri can host multiple webviews with distinct labels (titlebar, ask, settings), you can render entirely separate React trees without a client-side router. For single-webview apps, standard React Router works normally within the WebView.
How do I handle window controls like minimize and fullscreen in React?
Import getCurrentWindow from @tauri-apps/api/window to access the native window instance. Methods like win.minimize(), win.maximize(), and win.setFullscreen(bool) control the window state. Subscribe to state changes using win.listen('tauri://resize', callback) to keep your React UI synchronized with the native window.
What is the role of Vite in a Tauri React application?
Vite serves as the build tool and development server for the React frontend. It provides Hot Module Replacement during development and bundles the application into static files that Tauri embeds into the native binary. The vite.config.ts file in the ChatGPT repository configures the output directory and plugins required for the Tauri integration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →