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:
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
- Imports - All necessary imports at the top
- Component Definition - Functional component with destructured props
- State Management - useState, useEffect, and custom hooks
- Helper Functions - Small utility functions within the component
- JSX Return - Clean, semantic HTML with Tailwind classes
- Export - Default export of the component
State Management
Local Component State
Use useState
for local component state:
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:
// 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:
const MyComponent = ({
title,
items = [],
isLoading = false,
onAction = () => {}
}) => {
// Component implementation
};
Event Handling
Use inline arrow functions or separate handler functions:
// 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
- Layout classes (flex, grid, etc.) first
- Positioning (relative, absolute, etc.)
- Sizing (w-, h-, etc.)
- Spacing (m-, p-, etc.)
- Typography (text-, font-, etc.)
- Visual (bg-, border-, shadow-, etc.)
- Interactive states (hover:, focus:, etc.)
<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:
<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:
<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:
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:
// 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:
// 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:
- Semantic HTML - Use appropriate HTML elements
- ARIA attributes - When needed for dynamic content
- Keyboard navigation - Ensure all interactive elements are keyboard accessible
- Focus management - Proper focus handling for modals, dropdowns, etc.
- Screen reader support - Use aria-label, aria-describedby, etc.
<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:
const MyComponent = React.memo(({ data, onUpdate }) => {
// Component implementation
});
export default MyComponent;
useCallback and useMemo
Optimize expensive calculations and callback functions:
const expensiveValue = useMemo(() => {
return computeExpensiveValue(data);
}, [data]);
const handleClick = useCallback((id) => {
dispatch(action(id));
}, [dispatch]);
Lazy Loading
Use React.lazy for code splitting:
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:
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:
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:
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:
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:
<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+) andmd:
(1024px+) - Desktop:
lg:
(1280px+) andxl:
(1536px+)
Mobile-First Approach
Start with mobile styles and enhance for larger screens:
<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
- Single Responsibility - Each component should have one clear purpose
- Reusability - Design components to be reusable across the application
- Composition - Build complex UIs by composing simpler components
- Controlled Components - Prefer controlled components for form elements
- Props Drilling - Use context or Redux to avoid excessive prop drilling
Code Organization
- Consistent Naming - Use consistent naming conventions (PascalCase for components)
- Logical Grouping - Group related files in directories
- Export Strategy - Use default exports for components, named exports for utilities
- Import Organization - Group imports logically (external, internal, styles)
Performance
- Bundle Size - Monitor bundle size and optimize when necessary
- Rendering - Use React.memo, useMemo, and useCallback appropriately
- API Calls - Implement caching and pagination where appropriate
- Images - Optimize images and use lazy loading
Security
- XSS Prevention - React automatically escapes content, but be careful with dangerouslySetInnerHTML
- Token Storage - Store JWT tokens securely (HttpOnly cookies or secure localStorage)
- Input Validation - Validate and sanitize user inputs
- 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.