| # 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. |