# How the Tauri v2 Desktop App Integrates with the React Frontend for Filesystem Dialogs in GeoLibre

> Learn how GeoLibre's Tauri v2 desktop app connects React components to native filesystem dialogs using a TypeScript bridge and Tauri's invoke API, with browser fallbacks.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-05

---

**GeoLibre uses a thin TypeScript bridge in [`apps/geolibre-desktop/src/lib/tauri-io.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/tauri-io.ts) to let React components invoke native file dialogs through Tauri v2's `invoke` API and `@tauri-apps/plugin-dialog`, with automatic browser fallbacks when running outside the desktop shell.**

The `opengeos/GeoLibre` repository is a geospatial data viewer that ships both as a Tauri v2 desktop application and a browser-based build. A critical integration point is how the React frontend requests filesystem access—file open, save, and folder selection dialogs—without sacrificing security or cross-platform compatibility. This article examines the exact mechanism, key source files, and code patterns used to bridge React and Tauri's native capabilities.

---

## Tauri v2 Command Registration and Security Model

Tauri v2 exposes native functionality through **commands**—Rust functions registered with the Tauri runtime that JavaScript can call via `invoke`. In GeoLibre, the Rust side (located in [`src-tauri/src/main.rs`](https://github.com/opengeos/GeoLibre/blob/main/src-tauri/src/main.rs) and related modules) registers commands like `read_local_file`, `fetch_url_bytes`, and `allow_raster_asset`.

These commands follow Tauri's **Powerbox security model**: the renderer process never holds blanket filesystem permissions. Instead, the user grants access through native dialogs, and Tauri returns validated **file handles** that subsequent operations must use. This ensures the React UI cannot access arbitrary paths without explicit user consent.

---

## The TypeScript Bridge: [`tauri-io.ts`](https://github.com/opengeos/GeoLibre/blob/main/tauri-io.ts)

All frontend-to-Tauri communication for filesystem operations flows through [`apps/geolibre-desktop/src/lib/tauri-io.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/tauri-io.ts). This module centralizes dialog invocation and provides graceful degradation when the code runs in a browser environment.

### Core Imports and Wrappers

```typescript
// apps/geolibre-desktop/src/lib/tauri-io.ts
import { open, save } from "@tauri-apps/plugin-dialog";
import { invoke } from "@tauri-apps/api/core";

```

The file imports two distinct APIs:

- **`@tauri-apps/plugin-dialog`** – Provides `open()` and `save()` for native dialog windows
- **`@tauri-apps/api/core`** – Provides `invoke()` for calling custom Rust commands

### File Open Dialog with Extension Filtering

```typescript
export async function openFileDialog(
  filters: FileFilter[]
): Promise<string[] | null> {
  try {
    const paths = await open({ multiple: true, filters });
    return paths ?? null;  // null indicates user cancellation
  } catch (e) {
    console.warn("File dialog requires Tauri runtime");
    return null;
  }
}

```

**Key behaviors:**
- `multiple: true` allows batch selection
- `filters` restricts selectable file types (e.g., GeoJSON, Shapefile, Zarr)
- The `catch` block handles browser environments where Tauri APIs are unavailable

### Save Dialog for Export Operations

```typescript
export async function saveFileDialog(
  defaultName: string,
  filters: FileFilter[]
): Promise<string | null> {
  const path = await save({ defaultPath: defaultName, filters });
  return path ?? null;
}

```

### Invoking Rust Commands for File Reading

After obtaining a path from the dialog, the frontend reads file contents through a Rust command:

```typescript
export async function readLocalFile(path: string): Promise<ArrayBuffer> {
  return await invoke<ArrayBuffer>("read_local_file", { path });
}

```

The `invoke` call serializes the path argument, passes it to the Rust runtime, which validates the handle and returns the file's bytes as an `ArrayBuffer`.

---

## Directory Picking: [`zarr-directory-picker.ts`](https://github.com/opengeos/GeoLibre/blob/main/zarr-directory-picker.ts)

For folder selection—critical for Zarr dataset navigation—GeoLibre uses a dedicated module:

```typescript
// apps/geolibre-desktop/src/lib/zarr-directory-picker.ts
import { open } from "@tauri-apps/plugin-dialog";

export async function pickZarrDirectory(): Promise<string | null> {
  const path = await open({ directory: true });
  return path ?? null;
}

```

Setting `directory: true` invokes the platform-native folder picker. The returned path feeds into Zarr parsing logic that traverses the hierarchical array store.

---

## React Hook Integration Pattern

The React UI does not call [`tauri-io.ts`](https://github.com/opengeos/GeoLibre/blob/main/tauri-io.ts) functions directly. Instead, **custom hooks** abstract the interaction and dispatch results to the central store.

### Hook-to-Store Data Flow

1. User clicks "Add Data" button
2. Hook calls `openFileDialog()` from [`tauri-io.ts`](https://github.com/opengeos/GeoLibre/blob/main/tauri-io.ts)
3. On success, hook dispatches to `@geolibre/core` store
4. Store thunk invokes `readLocalFile()` via the same bridge
5. Binary data flows back to React for rendering

```typescript
// Example pattern from hooks like usePlugins.ts
import { openFileDialog, readLocalFile } from "@/lib/tauri-io";
import { useAppStore } from "@geolibre/core";

export function useDataImport() {
  const addLayer = useAppStore(state => state.addLayer);

  const importVectorFiles = async () => {
    const files = await openFileDialog([
      { name: "GeoJSON", extensions: ["geojson", "json"] },
      { name: "Shapefile", extensions: ["shp"] }
    ]);
    
    if (!files) return;

    for (const path of files) {
      const buffer = await readLocalFile(path);
      // Parse and add to map layers...
      addLayer({ type: "vector", source: buffer, path });
    }
  };

  return { importVectorFiles };
}

```

This **store-driven architecture** decouples UI components from Tauri specifics—identical React code works in the desktop app and browser builds.

---

## Browser Fallback Mechanism

When [`tauri-io.ts`](https://github.com/opengeos/GeoLibre/blob/main/tauri-io.ts) detects that Tauri APIs are unavailable (caught exceptions or undefined globals), the same module can delegate to standard Web APIs:

- **File selection**: Hidden `<input type="file">` element
- **Directory selection**: Experimental File System Access API (`showDirectoryPicker`)
- **Save/export**: `URL.createObjectURL` with anchor download, or `showSaveFilePicker`

This dual-target compilation allows GeoLibre to ship a single React codebase for both Tauri v2 desktop and embedded Jupyter/web deployments.

---

## Security and Sandboxing Implementation

| Layer | Protection |
|-------|-----------|
| **Tauri runtime** | Renderer process runs without filesystem permissions |
| **Dialog grants** | User action (click) required to obtain file handles |
| **Command validation** | Rust `read_local_file` validates handles before access |
| **Path isolation** | React never constructs absolute paths; only uses dialog returns |

The React frontend receives **opaque path strings**—it cannot traverse parent directories or access sibling files. All file operations route through validated Tauri commands.

---

## Summary

- **Central bridge**: [`apps/geolibre-desktop/src/lib/tauri-io.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/tauri-io.ts) wraps all Tauri v2 dialog and invoke calls
- **Plugin architecture**: Uses `@tauri-apps/plugin-dialog` for native selectors, `@tauri-apps/api/core` for custom Rust commands
- **Hook abstraction**: React components use store-connected hooks that delegate to the bridge
- **Cross-platform**: Automatic fallback to Web APIs when Tauri runtime is absent
- **Security model**: Powerbox-style grant validation in Rust commands prevents unauthorized access

---

## Frequently Asked Questions

### How does GeoLibre handle file dialogs in browser builds?

GeoLibre's [`tauri-io.ts`](https://github.com/opengeos/GeoLibre/blob/main/tauri-io.ts) module catches exceptions when Tauri APIs are unavailable and falls back to standard Web APIs like `<input type="file">`, `showDirectoryPicker`, or `showSaveFilePicker`. The same React components work without modification because the bridge abstracts the platform difference.

### What Tauri v2 plugin provides the folder picker for Zarr datasets?

The `@tauri-apps/plugin-dialog` package provides folder selection through `open({ directory: true })`. GeoLibre wraps this in [`apps/geolibre-desktop/src/lib/zarr-directory-picker.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/zarr-directory-picker.ts) with Zarr-specific error handling and path validation.

### Why doesn't the React frontend read files directly?

Direct filesystem access from a web renderer violates OS security models. Tauri v2 enforces a **command-based architecture** where all file operations execute in the Rust host process with explicitly granted permissions. React receives only `ArrayBuffer` data or path references that the runtime has pre-validated.

### Can I use the same dialog code for both single and batch file selection?

Yes. The `openFileDialog` wrapper in [`tauri-io.ts`](https://github.com/opengeos/GeoLibre/blob/main/tauri-io.ts) passes `multiple: true` to the underlying `open()` call, returning `string[] | null`. For single-file scenarios, pass `multiple: false` or simply use the first element of the returned array.