|
# React Development Guide for Lin Project |
|
|
|
## Overview |
|
|
|
This guide documents the React development patterns, best practices, and conventions used in the Lin project. It serves as a reference for current and future developers working on the frontend. |
|
|
|
## Project Structure |
|
|
|
The frontend follows a component-based architecture with clear separation of concerns: |
|
|
|
``` |
|
frontend/ |
|
βββ src/ |
|
β βββ components/ # Reusable UI components |
|
β β βββ Header/ # Header component |
|
β β βββ Sidebar/ # Sidebar navigation |
|
β β βββ ... # Other reusable components |
|
β βββ pages/ # Page-level components |
|
β βββ services/ # API service layer |
|
β βββ store/ # Redux store configuration |
|
β βββ App.jsx # Root component |
|
β βββ index.jsx # Entry point |
|
βββ public/ # Static assets |
|
βββ package.json # Dependencies and scripts |
|
``` |
|
|
|
## Core Technologies |
|
|
|
- React 18+ with Hooks |
|
- React Router v6 for routing |
|
- Redux Toolkit for state management |
|
- Axios for HTTP requests |
|
- Tailwind CSS for styling |
|
- Material Icons for icons |
|
|
|
## Component Development Patterns |
|
|
|
### Functional Components with Hooks |
|
|
|
All components are implemented as functional components using React hooks: |
|
|
|
```jsx |
|
import React, { useState, useEffect } from 'react'; |
|
|
|
const MyComponent = ({ prop1, prop2 }) => { |
|
const [state, setState] = useState(initialValue); |
|
|
|
useEffect(() => { |
|
// Side effects |
|
}, [dependencies]); |
|
|
|
return ( |
|
<div className="component-class"> |
|
{/* JSX */} |
|
</div> |
|
); |
|
}; |
|
|
|
export default MyComponent; |
|
``` |
|
|
|
### Component Structure |
|
|
|
1. **Imports** - All necessary imports at the top |
|
2. **Component Definition** - Functional component with destructured props |
|
3. **State Management** - useState, useEffect, and custom hooks |
|
4. **Helper Functions** - Small utility functions within the component |
|
5. **JSX Return** - Clean, semantic HTML with Tailwind classes |
|
6. **Export** - Default export of the component |
|
|
|
### State Management |
|
|
|
#### Local Component State |
|
|
|
Use `useState` for local component state: |
|
|
|
```jsx |
|
const [isOpen, setIsOpen] = useState(false); |
|
const [data, setData] = useState([]); |
|
const [loading, setLoading] = useState(false); |
|
``` |
|
|
|
#### Global State (Redux) |
|
|
|
Use Redux Toolkit for global state management. Structure slices by feature: |
|
|
|
```jsx |
|
// store/reducers/featureSlice.js |
|
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; |
|
|
|
export const fetchFeatureData = createAsyncThunk( |
|
'feature/fetchData', |
|
async (params) => { |
|
const response = await api.getFeatureData(params); |
|
return response.data; |
|
} |
|
); |
|
|
|
const featureSlice = createSlice({ |
|
name: 'feature', |
|
initialState: { |
|
items: [], |
|
loading: false, |
|
error: null |
|
}, |
|
reducers: { |
|
clearError: (state) => { |
|
state.error = null; |
|
} |
|
}, |
|
extraReducers: (builder) => { |
|
builder |
|
.addCase(fetchFeatureData.pending, (state) => { |
|
state.loading = true; |
|
}) |
|
.addCase(fetchFeatureData.fulfilled, (state, action) => { |
|
state.loading = false; |
|
state.items = action.payload; |
|
}) |
|
.addCase(fetchFeatureData.rejected, (state, action) => { |
|
state.loading = false; |
|
state.error = action.error.message; |
|
}); |
|
} |
|
}); |
|
|
|
export const { clearError } = featureSlice.actions; |
|
export default featureSlice.reducer; |
|
``` |
|
|
|
### Props and Prop Types |
|
|
|
Destructure props in the component signature and provide default values when appropriate: |
|
|
|
```jsx |
|
const MyComponent = ({ |
|
title, |
|
items = [], |
|
isLoading = false, |
|
onAction = () => {} |
|
}) => { |
|
// Component implementation |
|
}; |
|
``` |
|
|
|
### Event Handling |
|
|
|
Use inline arrow functions or separate handler functions: |
|
|
|
```jsx |
|
// Inline |
|
<button onClick={() => handleClick(item.id)}>Click me</button> |
|
|
|
// Separate function |
|
const handleDelete = (id) => { |
|
dispatch(deleteItem(id)); |
|
}; |
|
|
|
<button onClick={() => handleDelete(item.id)}>Delete</button> |
|
``` |
|
|
|
## Styling with Tailwind CSS |
|
|
|
The project uses Tailwind CSS for styling. Follow these conventions: |
|
|
|
### Class Organization |
|
|
|
1. **Layout classes** (flex, grid, etc.) first |
|
2. **Positioning** (relative, absolute, etc.) |
|
3. **Sizing** (w-, h-, etc.) |
|
4. **Spacing** (m-, p-, etc.) |
|
5. **Typography** (text-, font-, etc.) |
|
6. **Visual** (bg-, border-, shadow-, etc.) |
|
7. **Interactive states** (hover:, focus:, etc.) |
|
|
|
```jsx |
|
<div className="flex items-center justify-between w-full p-4 bg-white rounded-lg shadow hover:shadow-md focus:outline-none focus:ring-2 focus:ring-primary-500"> |
|
{/* Content */} |
|
</div> |
|
``` |
|
|
|
### Responsive Design |
|
|
|
Use Tailwind's responsive prefixes (sm:, md:, lg:, xl:) for responsive styles: |
|
|
|
```jsx |
|
<div className="flex flex-col lg:flex-row items-center p-4 sm:p-6"> |
|
<div className="w-full lg:w-1/2 mb-4 lg:mb-0 lg:mr-6"> |
|
{/* Content */} |
|
</div> |
|
<div className="w-full lg:w-1/2"> |
|
{/* Content */} |
|
</div> |
|
</div> |
|
``` |
|
|
|
### Custom Classes |
|
|
|
For complex components, use component-specific classes in conjunction with Tailwind: |
|
|
|
```jsx |
|
<NavLink |
|
to={item.path} |
|
className={({ isActive }) => ` |
|
nav-link group relative flex items-center px-3 py-2.5 text-sm font-medium rounded-lg |
|
transition-all duration-200 ease-in-out |
|
${isActive |
|
? 'bg-gradient-to-r from-primary-600 to-primary-700 text-white' |
|
: 'text-secondary-700 hover:bg-accent-100' |
|
} |
|
`} |
|
> |
|
``` |
|
|
|
## Routing |
|
|
|
Use React Router v6 for navigation: |
|
|
|
```jsx |
|
import { Routes, Route, useNavigate, useLocation } from 'react-router-dom'; |
|
|
|
// In App.jsx |
|
<Routes> |
|
<Route path="/dashboard" element={<Dashboard />} /> |
|
<Route path="/sources" element={<Sources />} /> |
|
<Route path="/posts" element={<Posts />} /> |
|
<Route path="/schedule" element={<Schedule />} /> |
|
</Routes> |
|
|
|
// In components |
|
const navigate = useNavigate(); |
|
const location = useLocation(); |
|
|
|
// Navigation |
|
navigate('/dashboard'); |
|
|
|
// Check current route |
|
if (location.pathname === '/dashboard') { |
|
// Do something |
|
} |
|
``` |
|
|
|
## API Integration |
|
|
|
### Service Layer |
|
|
|
Create service functions for API calls: |
|
|
|
```jsx |
|
// services/api.js |
|
import axios from 'axios'; |
|
|
|
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:5000'; |
|
|
|
const api = axios.create({ |
|
baseURL: API_BASE_URL, |
|
timeout: 10000, |
|
}); |
|
|
|
// Request interceptor |
|
api.interceptors.request.use((config) => { |
|
const token = localStorage.getItem('token'); |
|
if (token) { |
|
config.headers.Authorization = `Bearer ${token}`; |
|
} |
|
return config; |
|
}); |
|
|
|
// Response interceptor |
|
api.interceptors.response.use( |
|
(response) => response, |
|
(error) => { |
|
if (error.response?.status === 401) { |
|
// Handle unauthorized access |
|
localStorage.removeItem('token'); |
|
window.location.href = '/login'; |
|
} |
|
return Promise.reject(error); |
|
} |
|
); |
|
|
|
export default api; |
|
|
|
// services/featureService.js |
|
import api from './api'; |
|
|
|
export const getFeatures = async () => { |
|
const response = await api.get('/api/features'); |
|
return response.data; |
|
}; |
|
|
|
export const createFeature = async (data) => { |
|
const response = await api.post('/api/features', data); |
|
return response.data; |
|
}; |
|
``` |
|
|
|
### Async Operations with Redux Thunks |
|
|
|
Use createAsyncThunk for asynchronous operations: |
|
|
|
```jsx |
|
// In slice |
|
export const fetchData = createAsyncThunk( |
|
'feature/fetchData', |
|
async (_, { rejectWithValue }) => { |
|
try { |
|
const data = await featureService.getFeatures(); |
|
return data; |
|
} catch (error) { |
|
return rejectWithValue(error.response.data); |
|
} |
|
} |
|
); |
|
|
|
// In component |
|
const dispatch = useDispatch(); |
|
const { items, loading, error } = useSelector(state => state.feature); |
|
|
|
useEffect(() => { |
|
dispatch(fetchData()); |
|
}, [dispatch]); |
|
``` |
|
|
|
## Accessibility (a11y) |
|
|
|
Implement proper accessibility features: |
|
|
|
1. **Semantic HTML** - Use appropriate HTML elements |
|
2. **ARIA attributes** - When needed for dynamic content |
|
3. **Keyboard navigation** - Ensure all interactive elements are keyboard accessible |
|
4. **Focus management** - Proper focus handling for modals, dropdowns, etc. |
|
5. **Screen reader support** - Use aria-label, aria-describedby, etc. |
|
|
|
```jsx |
|
<button |
|
aria-label="Close dialog" |
|
aria-expanded={isOpen} |
|
onClick={handleClose} |
|
> |
|
β |
|
</button> |
|
|
|
<nav aria-label="Main navigation"> |
|
<ul role="menubar"> |
|
<li role="none"> |
|
<a |
|
href="/dashboard" |
|
role="menuitem" |
|
aria-current={currentPage === 'dashboard' ? 'page' : undefined} |
|
> |
|
Dashboard |
|
</a> |
|
</li> |
|
</ul> |
|
</nav> |
|
``` |
|
|
|
## Performance Optimization |
|
|
|
### Memoization |
|
|
|
Use React.memo for components that render frequently: |
|
|
|
```jsx |
|
const MyComponent = React.memo(({ data, onUpdate }) => { |
|
// Component implementation |
|
}); |
|
|
|
export default MyComponent; |
|
``` |
|
|
|
### useCallback and useMemo |
|
|
|
Optimize expensive calculations and callback functions: |
|
|
|
```jsx |
|
const expensiveValue = useMemo(() => { |
|
return computeExpensiveValue(data); |
|
}, [data]); |
|
|
|
const handleClick = useCallback((id) => { |
|
dispatch(action(id)); |
|
}, [dispatch]); |
|
``` |
|
|
|
### Lazy Loading |
|
|
|
Use React.lazy for code splitting: |
|
|
|
```jsx |
|
import { lazy, Suspense } from 'react'; |
|
|
|
const LazyComponent = lazy(() => import('./components/MyComponent')); |
|
|
|
<Suspense fallback={<div>Loading...</div>}> |
|
<LazyComponent /> |
|
</Suspense> |
|
``` |
|
|
|
## Error Handling |
|
|
|
### Error Boundaries |
|
|
|
Implement error boundaries for catching JavaScript errors: |
|
|
|
```jsx |
|
class ErrorBoundary extends React.Component { |
|
constructor(props) { |
|
super(props); |
|
this.state = { hasError: false }; |
|
} |
|
|
|
static getDerivedStateFromError(error) { |
|
return { hasError: true }; |
|
} |
|
|
|
componentDidCatch(error, errorInfo) { |
|
console.error('Error caught by boundary:', error, errorInfo); |
|
} |
|
|
|
render() { |
|
if (this.state.hasError) { |
|
return <h1>Something went wrong.</h1>; |
|
} |
|
|
|
return this.props.children; |
|
} |
|
} |
|
|
|
// Usage |
|
<ErrorBoundary> |
|
<MyComponent /> |
|
</ErrorBoundary> |
|
``` |
|
|
|
### API Error Handling |
|
|
|
Handle API errors gracefully: |
|
|
|
```jsx |
|
const [error, setError] = useState(null); |
|
|
|
const handleSubmit = async (data) => { |
|
try { |
|
setError(null); |
|
const result = await api.createItem(data); |
|
// Handle success |
|
} catch (err) { |
|
setError(err.response?.data?.message || 'An error occurred'); |
|
} |
|
}; |
|
|
|
{error && ( |
|
<div className="text-red-500 text-sm mt-2"> |
|
{error} |
|
</div> |
|
)} |
|
``` |
|
|
|
## Testing |
|
|
|
### Unit Testing |
|
|
|
Use Jest and React Testing Library for unit tests: |
|
|
|
```jsx |
|
import { render, screen, fireEvent } from '@testing-library/react'; |
|
import MyComponent from './MyComponent'; |
|
|
|
test('renders component with title', () => { |
|
render(<MyComponent title="Test Title" />); |
|
expect(screen.getByText('Test Title')).toBeInTheDocument(); |
|
}); |
|
|
|
test('calls onClick when button is clicked', () => { |
|
const handleClick = jest.fn(); |
|
render(<MyComponent onClick={handleClick} />); |
|
fireEvent.click(screen.getByRole('button')); |
|
expect(handleClick).toHaveBeenCalledTimes(1); |
|
}); |
|
``` |
|
|
|
### Redux Testing |
|
|
|
Test Redux slices and async thunks: |
|
|
|
```jsx |
|
import featureReducer, { fetchData } from './featureSlice'; |
|
|
|
test('handles fulfilled fetch data', () => { |
|
const initialState = { items: [], loading: false, error: null }; |
|
const data = [{ id: 1, name: 'Test' }]; |
|
const action = { type: fetchData.fulfilled, payload: data }; |
|
const state = featureReducer(initialState, action); |
|
expect(state.items).toEqual(data); |
|
expect(state.loading).toBe(false); |
|
}); |
|
``` |
|
|
|
## Mobile Responsiveness |
|
|
|
### Touch Optimization |
|
|
|
Add touch-specific classes and handlers: |
|
|
|
```jsx |
|
<button |
|
className="touch-manipulation active:scale-95" |
|
onTouchStart={handleTouchStart} |
|
onTouchEnd={handleTouchEnd} |
|
> |
|
Click me |
|
</button> |
|
``` |
|
|
|
### Responsive Breakpoints |
|
|
|
Use Tailwind's responsive utilities for different screen sizes: |
|
|
|
- Mobile: Default styles (0-767px) |
|
- Tablet: `sm:` (768px+) and `md:` (1024px+) |
|
- Desktop: `lg:` (1280px+) and `xl:` (1536px+) |
|
|
|
### Mobile-First Approach |
|
|
|
Start with mobile styles and enhance for larger screens: |
|
|
|
```jsx |
|
<div className="flex flex-col lg:flex-row"> |
|
<div className="w-full lg:w-1/2"> |
|
{/* Content that stacks on mobile, side-by-side on desktop */} |
|
</div> |
|
</div> |
|
``` |
|
|
|
## Best Practices |
|
|
|
### Component Design |
|
|
|
1. **Single Responsibility** - Each component should have one clear purpose |
|
2. **Reusability** - Design components to be reusable across the application |
|
3. **Composition** - Build complex UIs by composing simpler components |
|
4. **Controlled Components** - Prefer controlled components for form elements |
|
5. **Props Drilling** - Use context or Redux to avoid excessive prop drilling |
|
|
|
### Code Organization |
|
|
|
1. **Consistent Naming** - Use consistent naming conventions (PascalCase for components) |
|
2. **Logical Grouping** - Group related files in directories |
|
3. **Export Strategy** - Use default exports for components, named exports for utilities |
|
4. **Import Organization** - Group imports logically (external, internal, styles) |
|
|
|
### Performance |
|
|
|
1. **Bundle Size** - Monitor bundle size and optimize when necessary |
|
2. **Rendering** - Use React.memo, useMemo, and useCallback appropriately |
|
3. **API Calls** - Implement caching and pagination where appropriate |
|
4. **Images** - Optimize images and use lazy loading |
|
|
|
### Security |
|
|
|
1. **XSS Prevention** - React automatically escapes content, but be careful with dangerouslySetInnerHTML |
|
2. **Token Storage** - Store JWT tokens securely (HttpOnly cookies or secure localStorage) |
|
3. **Input Validation** - Validate and sanitize user inputs |
|
4. **CORS** - Ensure proper CORS configuration on the backend |
|
|
|
This guide should help maintain consistency and quality across the React frontend implementation in the Lin project. |