Lin / REACT_DEVELOPMENT_GUIDE.md
Zelyanoth's picture
fff
25f22bf
|
raw
history blame
13.6 kB

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

  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:

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

  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.)
<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:

  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.
<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+) and md: (1024px+)
  • Desktop: lg: (1280px+) and xl: (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

  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.