| name | react-typescript-frontend |
| description | Build React TypeScript frontends with Mantine UI v8, Vite, and type-safe API integrations. Use when creating or modifying the Chuuk Dictionary frontend, building React components, or working with TypeScript in the frontend. Use when this capability is needed. |
| metadata | {"author":"findinfinitelabs"} |
React TypeScript Frontend
Overview
Build and maintain the Chuuk Dictionary React frontend using TypeScript, Mantine UI v8, and Vite. Focuses on type-safe development, component patterns, and proper Chuukese text handling.
Tech Stack
- React 19: Latest React with hooks and concurrent features
- TypeScript 5.9: Strict type checking
- Mantine v8: UI component library with dark mode
- Vite 7: Fast build tool and dev server
- React Router v7: Client-side routing
- Axios: HTTP client for API calls
Project Structure
frontend/
├── src/
│ ├── App.tsx # Main app component
│ ├── main.tsx # Entry point with providers
│ ├── theme.ts # Mantine theme configuration
│ ├── components/ # Reusable components
│ │ ├── Footer.tsx
│ │ └── GrammarLearning.tsx
│ ├── contexts/ # React contexts
│ ├── hooks/ # Custom hooks
│ ├── pages/ # Page components
│ ├── data/ # Static data
│ └── assets/ # Images, fonts
├── public/ # Static assets
├── index.html # HTML template
├── vite.config.ts # Vite configuration
├── tsconfig.json # TypeScript config
└── package.json
Configuration
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:5002',
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
sourcemap: true,
},
});
Component Patterns
1. Page Component
import { useState, useEffect } from 'react';
import { Container, Title, TextInput, Stack, Loader, Alert } from '@mantine/core';
import { IconSearch, IconAlertCircle } from '@tabler/icons-react';
import { useDictionary } from '@/hooks/useDictionary';
import { DictionaryEntry } from '@/components/DictionaryEntry';
interface DictionaryPageProps {
initialQuery?: string;
}
export function DictionaryPage({ initialQuery = '' }: DictionaryPageProps) {
const [query, setQuery] = useState(initialQuery);
const { entries, loading, error, search } = useDictionary();
useEffect(() => {
if (query.length >= 2) {
search(query);
}
}, [query, search]);
return (
<Container size= =>
Chuukese Dictionary
setQuery(e.target.value)}
leftSection={}
size="lg"
mb="xl"
styles={{
input: {
fontFamily: "'Noto Sans', sans-serif",
}
}}
/>
{error && (
} color="red" mb="md">
{error}
)}
{loading ? (
) : (
{entries.map((entry) => (
))}
)}
);
}
2. Reusable Component
import { Card, Text, Badge, Group, Stack } from '@mantine/core';
interface DictionaryEntryData {
_id: string;
chuukese_word: string;
english_definition: string;
part_of_speech?: string;
grammar_type?: string;
pronunciation?: string;
}
interface DictionaryEntryProps {
entry: DictionaryEntryData;
onClick?: (entry: DictionaryEntryData) => void;
}
export function DictionaryEntry({ entry, onClick }: DictionaryEntryProps) {
return (
<Card
shadow="sm"
padding="lg"
radius="md"
withBorder
onClick={() => onClick?.(entry)}
style={{ cursor: onClick ? 'pointer' : 'default' }}
>
<Group = =>
{entry.chuukese_word}
{entry.part_of_speech && (
{entry.part_of_speech}
)}
{entry.english_definition}
{entry.pronunciation && (
/{entry.pronunciation}/
)}
);
}
3. Custom Hook
import { useState, useCallback } from 'react';
import axios from 'axios';
interface DictionaryEntry {
_id: string;
chuukese_word: string;
english_definition: string;
part_of_speech?: string;
grammar_type?: string;
}
interface UseDictionaryResult {
entries: DictionaryEntry[];
loading: boolean;
error: string | null;
search: (query: string) => Promise<void>;
getEntry: (word: string) => Promise<DictionaryEntry | null>;
}
export function useDictionary(): UseDictionaryResult {
const [entries, setEntries] = useState<DictionaryEntry[]>([]);
const [loading, setLoading] = useState(false);
[error, setError] = useState< | >();
search = ( (: ) => {
(!query.()) {
([]);
;
}
();
();
{
response = axios.<{ : [] }>(
,
{ : { query } }
);
(response..);
} (err) {
(err ? err. : );
([]);
} {
();
}
}, []);
getEntry = ( (: ): < | > => {
{
response = axios.<>(
);
response.;
} {
;
}
}, []);
{ entries, loading, error, search, getEntry };
}
4. Context Provider
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import axios from 'axios';
interface User {
email: string;
name: string;
role: string;
}
interface AuthContextType {
user: User | null;
loading: boolean;
login: (email: string) => Promise<void>;
logout: () => Promise<void>;
isAuthenticated: boolean;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
( {
();
}, []);
= () => {
{
response = axios.<>();
(response.);
} {
();
} {
();
}
};
= () => {
axios.(, { email });
};
= () => {
axios.();
();
};
(
);
}
() {
context = ();
(context === ) {
();
}
context;
}
API Integration
Axios Setup
import axios from 'axios';
const apiClient = axios.create({
baseURL: '/api',
headers: {
'Content-Type': 'application/json',
},
withCredentials: true,
});
apiClient.interceptors.request.use((config) => {
return config;
});
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default apiClient;
Type-Safe API Calls
import apiClient from './client';
interface TranslationRequest {
text: string;
direction: 'chk_to_en' | 'en_to_chk';
}
interface TranslationResponse {
original: string;
translated: string;
confidence: number;
model_used: string;
}
export async function translate(request: TranslationRequest): Promise<TranslationResponse> {
const response = await apiClient.post<TranslationResponse>('/translate', request);
return response.data;
}
export async function translateBatch(
texts: string[],
direction: 'chk_to_en' | 'en_to_chk'
): Promise<TranslationResponse[]> {
const response = await apiClient.post<[]>(, {
texts,
direction,
});
response.;
}
Chuukese Text Handling
Font Configuration
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;600;700&display=swap');
:root {
font-family: 'Noto Sans', 'Arial Unicode MS', sans-serif;
}
.chuukese-text {
font-family: 'Noto Sans', 'Arial Unicode MS', sans-serif;
font-feature-settings: 'kern' 1, 'liga' 1;
line-height: 1.6;
}
Text Component with Accent Support
import { Text, TextProps } from '@mantine/core';
interface ChuukeseTextProps extends TextProps {
children: React.ReactNode;
}
export function ChuukeseText({ children, ...props }: ChuukeseTextProps) {
return (
<Text
{...props}
style={{
fontFamily: "'Noto Sans', 'Arial Unicode MS', sans-serif",
fontFeatureSettings: "'kern' 1, 'liga' 1",
...props.style,
}}
>
{children}
</Text>
);
}
Routing
Router Setup
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { Layout } from './components/Layout';
import { DictionaryPage } from './pages/DictionaryPage';
import { TranslationPage } from './pages/TranslationPage';
import { GrammarPage } from './pages/GrammarPage';
import { LoginPage } from './pages/LoginPage';
import { useAuth } from './contexts/AuthContext';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, loading } = useAuth();
if (loading) return <LoadingScreen />;
if (!isAuthenticated) return <Navigate to="/login" />;
return <>{children}</>;
}
export () {
(
);
}
Development Commands
npm install
npm run dev
npm run build
npm run preview
npm run lint
npx tsc --noEmit
Best Practices
TypeScript
- Enable strict mode: Catch more errors at compile time
- Define interfaces: Type all API responses and component props
- Avoid
any: Use proper types or unknown
- Use type guards: Narrow types safely
React
- Use functional components: Hooks for all state management
- Memoize expensive operations:
useMemo, useCallback
- Split components: Keep components focused and reusable
- Handle loading and error states: Always show feedback
Mantine
- Use theme tokens: Don't hardcode colors or spacing
- Leverage built-in components: Avoid custom implementations
- Use compound components: Group related components logically
- Dark mode first: Test in both color schemes
Dependencies
{
"dependencies": {
"@mantine/core": "^8.3.10",
"@mantine/hooks": "^8.3.10",
"@mantine/notifications": "^8.3.10",
"@mantine/modals": "^8.3.10",
"@tabler/icons-react": "^3.35.0",
"axios": "^1.13.2",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.10.1"
},
"devDependencies": {
"@vitejs/plugin-react": "^5.1.1",
"typescript": "~5.9.3",
"vite": "^7.2.4"
}
}
Converted and distributed by TomeVault — claim your Tome and manage your conversions.