React 18 Development Skill
Description
React 18 patterns for building modern UI components with TypeScript, hooks, and best practices.
Project Structure
code
src/ ├── components/ │ ├── layout/ # App shell, sidebar, header │ ├── chat/ # Chat-related components │ ├── search/ # Search UI │ ├── common/ # Shared components │ └── index.ts # Barrel exports ├── hooks/ # Custom React hooks ├── services/ # API clients ├── stores/ # Zustand stores ├── types/ # TypeScript types ├── utils/ # Helper functions ├── App.tsx └── main.tsx
Component Patterns
Functional Component
tsx
interface ChatMessageProps {
message: Message;
isUser: boolean;
onRetry?: () => void;
}
export function ChatMessage({ message, isUser, onRetry }: ChatMessageProps) {
const formattedTime = useFormattedTime(message.timestamp);
return (
<div className={cn(
"p-4 rounded-lg",
isUser ? "bg-blue-50 ml-auto" : "bg-gray-50"
)}>
<p>{message.content}</p>
<span className="text-xs text-gray-500">{formattedTime}</span>
</div>
);
}
Custom Hook
tsx
import { useState, useCallback } from 'react';
import { useMutation } from '@tanstack/react-query';
export function useChat() {
const [messages, setMessages] = useState<Message[]>([]);
const mutation = useMutation({
mutationFn: sendChatMessage,
onSuccess: (data) => {
setMessages(prev => [...prev, {
role: 'assistant',
content: data.answer,
sources: data.sources,
}]);
},
});
const sendMessage = useCallback((content: string) => {
setMessages(prev => [...prev, { role: 'user', content }]);
mutation.mutate(content);
}, [mutation]);
return {
messages,
sendMessage,
isLoading: mutation.isPending,
error: mutation.error,
};
}
State Management (Zustand)
tsx
import { create } from 'zustand';
interface AppState {
sidebarOpen: boolean;
currentView: 'chat' | 'search' | 'documents';
toggleSidebar: () => void;
setView: (view: AppState['currentView']) => void;
}
export const useAppStore = create<AppState>((set) => ({
sidebarOpen: true,
currentView: 'chat',
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
setView: (view) => set({ currentView: view }),
}));
Data Fetching (React Query)
tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function useDocuments() {
const queryClient = useQueryClient();
const query = useQuery({
queryKey: ['documents'],
queryFn: fetchDocuments,
staleTime: 5 * 60 * 1000, // 5 minutes
});
const syncMutation = useMutation({
mutationFn: triggerSync,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['documents'] });
},
});
return {
documents: query.data ?? [],
isLoading: query.isLoading,
triggerSync: syncMutation.mutate,
};
}
Error Boundary
tsx
import { Component, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
}
export class ErrorBoundary extends Component<Props, State> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <div>Something went wrong</div>;
}
return this.props.children;
}
}
Streaming Text Component
tsx
import { useState, useEffect } from 'react';
interface StreamingTextProps {
stream: AsyncIterable<string>;
}
export function StreamingText({ stream }: StreamingTextProps) {
const [text, setText] = useState('');
useEffect(() => {
const consume = async () => {
for await (const chunk of stream) {
setText(prev => prev + chunk);
}
};
consume();
}, [stream]);
return <p className="whitespace-pre-wrap">{text}</p>;
}
Best Practices
- •Co-locate - Keep related code together (component + hook + types)
- •Composition over inheritance - Use compound components
- •Memoization - Use
useMemo/useCallbackfor expensive operations - •Keys - Always use stable, unique keys for lists
- •Accessibility - Include ARIA attributes, keyboard navigation
- •Loading states - Always handle loading, error, and empty states
File Naming
- •Components:
PascalCase.tsx(e.g.,ChatWindow.tsx) - •Hooks:
useCamelCase.ts(e.g.,useChat.ts) - •Utils:
camelCase.ts(e.g.,formatDate.ts) - •Types:
camelCase.tsorindex.ts