# React 18+ (Vite) Platform Guide

## Contents
- Project scaffolding and inspection
- File structure
- Global styles
- Component generation
- Routing
- API services
- Environment configuration
- React-specific rules
- Code patterns

## Project scaffolding and inspection

**Existing project**: Read `src/App.tsx`, `src/main.tsx`, and `vite.config.ts` to understand current state (routing, providers, and layout)

**New project**:
```bash
npm create vite@latest <name> -- --template react-ts
cd <name>
npm install
```

## File structure

Each routed section becomes a directory under `src/components/`:

```
src/components/<name>/
├── <Name>.tsx
├── <Name>.module.css
└── <Name>Service.ts          # only if OpenAPI provided
```

## Global styles

Generate `:root` CSS custom properties in `src/index.css` following [design-tokens-guide.md](design-tokens-guide.md)

## Component generation

For each routed section, the orchestrator creates the `.tsx` file (with hooks, state, and service calls) and `Service.ts` (if OpenAPI provided). The JSX return block and `.module.css` are generated by the image-to-component procedure

Update:

- `src/App.tsx` — shared layout sections (`<header>`, `<nav>`, `<footer>`, `<aside>`) + `<Outlet />` where `<main>` was
- `src/main.tsx` — wrap `<App />` with `<BrowserRouter>` and configure routes

## Routing

Install `react-router-dom`:
```bash
npm install react-router-dom
```

```tsx
// src/main.tsx
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import App from './App';
import { lazy, Suspense } from 'react';

const Home = lazy(() => import('./components/home/Home'));
const Items = lazy(() => import('./components/items/Items'));

createRoot(document.getElementById('root')!).render(
	<BrowserRouter>
		<Routes>
			<Route element={<App />}>
				<Route index element={<Navigate to="/home" replace />} />
				<Route path="home" element={<Suspense fallback={null}><Home /></Suspense>} />
				<Route path="items" element={<Suspense fallback={null}><Items /></Suspense>} />
			</Route>
		</Routes>
	</BrowserRouter>
);
```

```tsx
// src/App.tsx
import { Outlet } from 'react-router-dom';

export default function App() {
	return (
		<>
			<header>{/* shared header */}</header>
			<main>
				<Outlet />
			</main>
			<footer>{/* shared footer */}</footer>
		</>
	);
}
```

## API services

### Environment configuration

```bash
# .env
VITE_API_URL=/rest
```

`VITE_API_URL` uses a **relative path** (e.g. `/rest`) so REST calls go through the Vite dev proxy (see [Dev proxy](#dev-proxy) below). Switching between mock and real backend is done by changing the proxy target — no code changes needed

### Service pattern

```typescript
// src/components/items/ItemsService.ts
const API_URL = import.meta.env.VITE_API_URL;

interface Item {
	id: number;
	title: string;
	imageUrl: string;
}

export async function getItems(): Promise<Item[]> {
	try {
		const response = await fetch(`${API_URL}/items`);
		if (!response.ok) throw new Error(`HTTP ${response.status}`);
		return await response.json();
	} catch (err) {
		console.error('API error:', err);
		return [];
	}
}
```

### Dev proxy

Configure the Vite dev proxy in `vite.config.ts` to proxy REST calls to the backend. This avoids CORS issues and allows cookies to be sent on same-origin requests. By default, the target points to the local mock server. Include a commented-out line with the real backend URL so switching is easy

```typescript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
	plugins: [react()],
	server: {
		proxy: {
			'/rest': {
				target: 'http://localhost:3001',        // Mock server
				// target: 'https://<real-backend-url>', // Real backend
				secure: false,
				changeOrigin: true
			}
		}
	}
});
```

Add proxy entries for any additional non-REST paths that also need proxying (e.g. chat or streaming endpoints identified in the OpenAPI spec)

### GeneXus backend

Apply when using a GeneXus backend. See [genexus-backend.md](genexus-backend.md) for the rules; below is the React implementation

**Image URL resolution**: Add `VITE_IMAGE_BASE_URL` to `.env` (absolute URL without `/rest`). Images resolve against `VITE_IMAGE_BASE_URL`, not `VITE_API_URL`:

```typescript
// In .env, add:
// VITE_IMAGE_BASE_URL=https://example.com/MyApp

// In service:
const IMAGE_BASE_URL = import.meta.env.VITE_IMAGE_BASE_URL;

export function resolveImageUrl(relativeUrl: string): string {
	if (!relativeUrl) return '';
	if (relativeUrl.startsWith('http')) return relativeUrl;
	return `${IMAGE_BASE_URL}/${relativeUrl}`;
}

// Image fallback: use CSS background on image wrapper containers (see SKILL.md Step 7 item 7)
```

**gxid**: Generate per component with `useRef`, pass to every service call:

```tsx
// Component:
const gxid = useRef(Math.floor(Math.random() * 32767));

useEffect(() => {
	getItems(gxid.current).then(setItems);
}, []);

// Service — accept gxid as parameter:
export async function getItems(gxid: number): Promise<Item[]> {
	try {
		const response = await fetch(`${API_URL}/items?gxid=${gxid}`);
		if (!response.ok) throw new Error(`HTTP ${response.status}`);
		return await response.json();
	} catch (err) {
		console.error('API error:', err);
		return [];
	}
}
```

**GX_CLIENT_ID cookie**: Create a helper that ensures the cookie exists and a fetch wrapper that sends credentials:

```typescript
// src/utils/gxClientId.ts
function getOrCreateGxClientId(): string {
	const cookieName = 'GX_CLIENT_ID';
	const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${cookieName}=([^;]*)`));
	if (match) return match[1];
	const guid = crypto.randomUUID();
	document.cookie = `${cookieName}=${guid}; path=/; SameSite=Lax`;
	return guid;
}

export function gxFetch(url: string, init?: RequestInit): Promise<Response> {
	getOrCreateGxClientId();
	return fetch(url, { ...init, credentials: 'include' });
}
```

Use `gxFetch` instead of `fetch` in all service methods when connecting to a GeneXus backend

**Endpoint call order**: Master first, then grids:

```tsx
useEffect(() => {
	getMaster(gxid.current).then(() => {
		getGrid1(gxid.current).then(setGrid1Items);
		getGrid2(gxid.current).then(setGrid2Items);
	});
}, []);
```

**Accept header override**: Use `vite.config.ts` proxy configuration to override the header:

```typescript
// vite.config.ts
export default defineConfig({
	plugins: [react()],
	server: {
		proxy: {
			'/rest': {
				target: 'http://localhost:3001',        // Mock server
				// target: 'https://example.com/MyApp', // Real backend
				secure: false,
				changeOrigin: true,
				configure: (proxy) => {
					proxy.on('proxyReq', (proxyReq) => {
						proxyReq.setHeader('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8');
					});
				}
			}
		}
	}
});
```

### Mock server

See [mock-server-template.md](mock-server-template.md) for the base template. Customize endpoints to match the OpenAPI spec

### Dev script

Add a `dev` script to `package.json` that runs the mock server and Vite dev server concurrently:

```json
{
	"scripts": {
		"dev": "concurrently \"npx tsx mock-server.ts\" \"vite\" --names mock,app",
		"dev:real": "vite"
	}
}
```

Install `concurrently` as a dev dependency: `npm install -D concurrently`

### Component with hooks

```tsx
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { getItems } from './ItemsService';
import type { Item } from './ItemsService';
import styles from './Items.module.css';

export default function Items() {
	const [items, setItems] = useState<Item[]>([]);
	const [loading, setLoading] = useState(true);
	const [error, setError] = useState<string | null>(null);

	useEffect(() => {
		getItems()
			.then(data => { setItems(data); setLoading(false); })
			.catch(err => { setError(err.message); setLoading(false); });
	}, []);

	if (loading) return null;
	if (error) return <p>{error}</p>;

	return (
		<div className={styles.container}>
			{items.map(item => (
				<article key={item.id} className={styles.card}>
					<h2>{item.title}</h2>
					<div className={styles.imageWrapper}>
						<img src={item.imageUrl} alt={item.title} />
					</div>
				</article>
			))}
		</div>
	);
}
```

## Interaction patterns

- **Optimistic updates**: For user actions with immediate visual feedback (toggles, favorites, quantity changes, likes), update the state instantly on click, then fire the server call. If the server call fails, revert the state to its previous value

## React-specific rules

- React 18+ / TypeScript — always `.tsx` files
- Functional components only — never class components
- `useState` / `useEffect` / `useRef` for state — no Redux or Zustand unless asked
- `inject()` equivalent: props or custom hooks for dependency passing
- CSS Modules (`<Name>.module.css`) for component styles — never inline styles or global class names
- `react-router-dom` v6+ for routing with `<Outlet>` layout pattern
- Lazy loading with `React.lazy()` + `<Suspense>`
- Native `fetch` for HTTP calls — never axios unless asked
- `import.meta.env.VITE_API_URL` for REST calls — never hardcoded URLs
- Default export for page components (required by `React.lazy()`)
- Named exports for service functions and types
