# API Integration Standards for React
This document outlines the coding standards for API integration in React applications. It aims to provide developers with guidelines for building robust, maintainable, and performant React applications that interact with backend services and external APIs. These standards are based on modern React principles and best practices.
## 1. Architectural Patterns
### 1.1 Separation of Concerns
**Standard:** Separate API interaction logic from UI components.
* **Do This:** Use a dedicated service layer or custom hooks to handle API calls.
* **Don't Do This:** Directly call APIs within React components.
**Why:** Improves code readability, testability, and reusability. UI components should focus on rendering data, not fetching it.
**Code Example:**
"""javascript
// apiService.js
import axios from 'axios';
const API_BASE_URL = 'https://api.example.com';
const apiService = {
getUsers: async () => {
try {
const response = await axios.get("${API_BASE_URL}/users");
return response.data;
} catch (error) {
console.error('Error fetching users:', error);
throw error; // Re-throw to allow component-level handling
}
},
createUser: async (userData) => {
try {
const response = await axios.post("${API_BASE_URL}/users", userData);
return response.data;
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
},
// Add other API methods here
};
export default apiService;
// UsersComponent.jsx
import React, { useState, useEffect } from 'react';
import apiService from './apiService';
function UsersComponent() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUsers = async () => {
try {
const data = await apiService.getUsers();
setUsers(data);
setLoading(false);
} catch (err) {
setError(err.message || 'Failed to fetch users');
setLoading(false);
}
};
fetchUsers();
}, []);
if (loading) return <p>Loading users...</p>;
if (error) return <p>Error: {error}</p>;
return (
{users.map(user => (
{user.name}
))}
);
}
export default UsersComponent;
"""
### 1.2 Custom Hooks for Data Fetching
**Standard:** Encapsulate data fetching logic within custom React hooks.
* **Do This:** Create reusable hooks like "useFetch" or "useData" to handle API calls and state management.
* **Don't Do This:** Repeat API fetching logic in multiple components.
**Why:** Promotes code reuse, simplifies component structure, and improves testability. Centralized error handling and loading state management.
**Code Example:**
"""javascript
// useFetch.js
import { useState, useEffect } from 'react';
import axios from 'axios';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(url);
setData(response.data);
setLoading(false);
} catch (err) {
setError(err.message || 'Failed to fetch data');
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
// MyComponent.jsx
import React from 'react';
import useFetch from './useFetch';
function MyComponent() {
const { data, loading, error } = useFetch('https://api.example.com/data');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
if (!data) return <p>No data available</p>;
return (
{/* Render data here */}
<pre>{JSON.stringify(data, null, 2)}</pre>
);
}
export default MyComponent;
"""
### 1.3 State Management Libraries (Redux, Zustand, Jotai)
**Standard:** Use a state management library for complex data flows and global state.
* **Do This:** Consider Redux, Zustand, or Jotai for managing API data that needs to be shared across multiple components.
* **Don't Do This:** Rely solely on prop drilling for deeply nested data.
**Why:** Simplifies data management in large applications, enhances predictability, and improves performance by minimizing re-renders.
**Code Example (Redux):**
"""javascript
// actions.js
export const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST';
export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE';
export const fetchUsersRequest = () => ({ type: FETCH_USERS_REQUEST });
export const fetchUsersSuccess = (users) => ({ type: FETCH_USERS_SUCCESS, payload: users });
export const fetchUsersFailure = (error) => ({ type: FETCH_USERS_FAILURE, payload: error });
export const fetchUsers = () => {
return async (dispatch) => {
dispatch(fetchUsersRequest());
try {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
dispatch(fetchUsersSuccess(data));
} catch (error) {
dispatch(fetchUsersFailure(error.message));
}
};
};
// reducers.js
import { FETCH_USERS_REQUEST, FETCH_USERS_SUCCESS, FETCH_USERS_FAILURE } from './actions';
const initialState = {
users: [],
loading: false,
error: null,
};
const usersReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USERS_REQUEST:
return { ...state, loading: true, error: null };
case FETCH_USERS_SUCCESS:
return { ...state, loading: false, users: action.payload };
case FETCH_USERS_FAILURE:
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
export default usersReducer;
// store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import usersReducer from './reducers';
const store = createStore(usersReducer, applyMiddleware(thunk));
export default store;
// UsersComponent.jsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUsers } from './actions';
function UsersComponent() {
const dispatch = useDispatch();
const { users, loading, error } = useSelector(state => state);
useEffect(() => {
dispatch(fetchUsers());
}, [dispatch]);
if (loading) return <p>Loading users...</p>;
if (error) return <p>Error: {error}</p>;
return (
{users.map(user => (
{user.name}
))}
);
}
export default UsersComponent;
"""
### 1.4 React Query or SWR
**Standard**: Use React Query or SWR for data fetching, caching, and state management tailored to server-state.
* **Do This:** Implement efficient data fetching and caching with automatic background updates using React Query or SWR, especially for frequently accessed or mutated data.
* **Don't Do This:** Re-implement caching logic or neglect background data synchronization.
**Why:** Reduces boilerplate for common data-fetching tasks, handles caching effectively, provides built-in features like retries and polling, leading to a better user experience.
**Code Example (React Query):**
"""javascript
// UsersComponent.jsx
import React from 'react';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
const fetchUsers = async () => {
const response = await axios.get('https://api.example.com/users');
return response.data;
};
function UsersComponent() {
const { data, isLoading, isError, error } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
if (isLoading) return <p>Loading users...</p>;
if (isError) return <p>Error: {error.message}</p>;
return (
{data.map(user => (
{user.name}
))}
);
}
export default UsersComponent;
"""
## 2. API Request Implementation
### 2.1 Using "fetch" or "axios"
**Standard:** Favor "axios" for its versatility but "fetch" is acceptable with proper error handling and configuration.
* **Do This:** Use "axios" or "fetch" with async/await syntax for cleaner code. Use "axios" for automatic JSON parsing, request cancellation, and interceptors. If using "fetch", handle error statuses appropriately.
* **Don't Do This:** Use XMLHttpRequest directly.
**Why:** Provides a more modern and readable approach to making API requests. "axios" offers advanced features that simplify common tasks, whereas "fetch" is built-in, avoiding external dependencies.
**Code Example ("axios"):**
"""javascript
import axios from 'axios';
const fetchData = async () => {
try {
const response = await axios.get('https://api.example.com/data');
console.log(response.data);
return response.data
} catch (error) {
console.error('Error fetching data:', error);
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2XX
console.error("Response data:", error.response.data);
console.error("Response status:", error.response.status);
console.error("Response headers:", error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// "error.request" is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in Node.js
console.error("Request but no response:", error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.error("Error Message:", error.message);
}
console.error("Error config:", error.config);
throw error; // Re-throw to allow component-level handling
}
};
export default fetchData;
"""
**Code Example ("fetch"):**
"""javascript
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error("HTTP error! Status: ${response.status}");
}
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error('Error fetching data:', error);
throw error; // Re-throw to allow component-level handling
}
};
export default fetchData;
"""
### 2.2 Environment Variables
**Standard:** Store API base URLs and sensitive keys in environment variables.
* **Do This:** Use ".env" files (with tools like "dotenv") and process.env when accessing API keys and endpoint URLs.
* **Don't Do This:** Hardcode API credentials or URLs in your code.
**Why:** Enhances security and makes it easier to manage different environments (development, staging, production).
**Code Example:**
"""javascript
// .env
API_BASE_URL=https://api.example.com
API_KEY=your_api_key
"""
"""javascript
// apiService.js
import axios from 'axios';
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL; // Access environment variable
const API_KEY = process.env.REACT_APP_API_KEY;
const apiService = {
getData: async () => {
try {
const response = await axios.get("${API_BASE_URL}/data", {
headers: {
'X-API-Key': API_KEY,
},
});
return response.data;
} catch (error) {
console.error('Error fetching data:', error);
throw error;
}
},
};
export default apiService;
"""
### 2.3 Request Cancellation
**Standard:** Implement request cancellation, particularly for long-running or frequently updated requests (e.g., autocomplete).
* **Do This:** Use "AbortController" with "fetch" or cancellation tokens in "axios".
* **Don't Do This:** Allow outdated requests to continue processing and potentially overwrite more recent data.
**Why:** Improves performance and prevents race conditions. Minimizes wasted resources.
**Code Example ("axios"):**
"""javascript
import axios from 'axios';
const fetchData = async (signal) => {
const source = axios.CancelToken.source();
const apiURL = 'https://api.example.com/search?q=';
try {
const response = await axios.get(apiURL, {
cancelToken: source.token,
signal: signal, // Optional: Propagate signal from parent component
});
return response.data;
} catch (error) {
if (axios.isCancel(error)) {
console.log('Request cancelled', error.message);
} else {
console.error('Error fetching data:', error);
throw error;
}
} finally {
source.cancel("Operation cancelled by the user.");
}
};
export default fetchData;
"""
"""javascript
// SearchComponent.jsx
import { useState, useEffect } from 'react';
import fetchData from './dataService';
function SearchComponent(){
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [controller, setController] = useState(new AbortController());
useEffect(() => {
const search = async () => {
if (!searchTerm) {
setSearchResults([]);
return;
}
setLoading(true);
setError(null);
// Cancel any previous requests
controller.abort();
const newController = new AbortController();
setController(newController);
try {
const results = await fetchData(newController.signal);
setSearchResults(results);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message || 'Search failed');
}
} finally {
setLoading(false);
}
};
search();
// Cleanup function to abort the request if the component unmounts
return () => controller.abort();
}, [searchTerm, controller]);
return ({/* ... */});
}
export default SearchComponent;
"""
**Code Example ("fetch"):**
"""javascript
import { useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [controller, setController] = useState(new AbortController());
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data', {
signal: controller.signal,
});
if (!response.ok) {
throw new Error("HTTP error! Status: ${response.status}");
}
const result = await response.json();
setData(result);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Fetch aborted');
} else {
setError(err.message || 'Failed to fetch data');
}
} finally {
setLoading(false);
}
};
fetchData();
return () => {
controller.abort(); // Cancel the fetch if the component unmounts
};
}, [controller]);
// ... rest of the component
}
export default MyComponent;
"""
### 2.4 Data Transformation
**Standard:** Transform API responses into a shape that is easily consumed by your components.
* **Do This:** Create transformation functions to convert API data into a consistent and manageable format.
* **Don't Do This:** Directly use the API response structure in your components, which tightly couple them to the API's schema.
**Why:** Decouples components from the API, making it easier to adapt to API changes without modifying component logic. Improves data consistency.
**Code Example:**
"""javascript
// apiService.js
import axios from 'axios';
const API_BASE_URL = 'https://api.example.com';
const transformUser = (user) => ({
id: user.user_id,
name: user.full_name,
email: user.email_address,
profilePicture: user.avatar_url || 'default_avatar.png',
});
const apiService = {
getUsers: async () => {
try {
const response = await axios.get("${API_BASE_URL}/users");
return response.data.map(transformUser); // Transform the API response
} catch (error) {
console.error('Error fetching users:', error);
throw error;
}
},
getUser: async (id) => {
try {
const response = await axios.get("${API_BASE_URL}/users/${id}");
return transformUser(response.data);
} catch (error) {
console.error("Error fetching user: ", error);
throw error;
}
}
};
export default apiService;
"""
## 3. Error Handling
### 3.1 Centralized Error Handling
**Standard:** Implement a centralized error handling mechanism.
* **Do This:** Use a global error boundary or a custom error handling function to catch and log errors.
* **Don't Do This:** Ignore API errors or handle them inconsistently across your application.
**Why:** Provides a consistent error handling strategy, makes it easier to debug issues, and prevents unhandled exceptions from crashing the application.
**Code Example (Error Boundary):**
"""javascript
// ErrorBoundary.jsx
import React, { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.error("Caught error: ", error, errorInfo);
//logErrorToMyService(error, errorInfo);
this.setState({
errorInfo:errorInfo
})
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (
Something went wrong.
{this.state.error && this.state.error.toString()}
<br>
{this.state.errorInfo && this.state.errorInfo.componentStack}
);
}
return this.props.children;
}
}
export default ErrorBoundary;
// App.js
import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import MyComponent from './MyComponent';
function App() {
return (
);
}
export default App;
"""
### 3.2 Specific Error Messages
**Standard:** Provide meaningful error messages to the user.
* **Do This:** Display user-friendly error messages based on the type of error (e.g., network error, server error, validation error).
* **Don't Do This:** Show generic "An error occurred" messages without any context.
**Why:** Improves user experience by helping users understand what went wrong and how to resolve the issue.
**Code Example:**
"""javascript
import React, { useState, useEffect } from 'react';
import apiService from './apiService';
function MyComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const result = await apiService.getData();
setData(result);
} catch (err) {
if (err.response && err.response.status === 404) {
setError('Resource not found.'); // Custom error message
} else if (err.message === 'Network Error') {
setError('A network error occurred. Please check your internet connection.'); // Custom error message
} else {
setError("An unexpected error occurred.")
}
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
{/* Render data here */}
);
}
export default MyComponent;
"""
### 3.3 Logging Errors
**Standard:** Log API errors for debugging and monitoring.
* **Do This:** Use a logging library (e.g., "console.error", Sentry, or LogRocket) to record errors, including stack traces and request details.
* **Don't Do This:** Rely solely on browser console logs in production environments or log sensitive information.
**Why:** Facilitates debugging, helps identify recurring issues, and provides insights into application health.
**Code Example:**
"""javascript
import axios from 'axios';
const fetchData = async () => {
try {
const response = await axios.get('https://api.example.com/data');
return response.data;
} catch (error) {
console.error('Error fetching data:', error);
// Log error to Sentry or your logging service
//Sentry.captureException(error);
throw error;
}
};
export default fetchData;
"""
## 4. Security Considerations
### 4.1 Authentication and Authorization
**Standard:** Implement proper authentication and authorization mechanisms.
* **Do This:** Secure your API endpoints with authentication (e.g., JWT, OAuth) and authorization (e.g., RBAC) to verify user identity and permissions.
* **Don't Do This:** Expose sensitive data without proper authentication or rely solely on client-side validation for security.
**Why:** Protects your application from unauthorized access and data breaches.
**Code Example (Attaching JWT token):**
"""javascript
import axios from 'axios';
const apiService = {
getData: async () => {
const token = localStorage.getItem('authToken'); // Retrieve token from storage
try {
const response = await axios.get('https://api.example.com/data', {
headers: {
Authorization: "Bearer ${token}", // Attach JWT token
},
});
return response.data;
} catch (error) {
console.error('Error fetching data:', error);
throw error;
}
},
};
export default apiService;
"""
### 4.2 Input Validation
**Standard:** Validate user inputs on both the client and server sides.
* **Do This:** Sanitize and validate data before sending it to the API to prevent injection attacks and other security vulnerabilities.
* **Don't Do This:** Trust user inputs without validation.
**Why:** Prevents malicious data from being injected into your application, protecting against XSS, SQL injection, and other attacks.
### 4.3 CORS Configuration
**Standard:** Configure CORS (Cross-Origin Resource Sharing) properly on your server.
* **Do This:** Set up CORS headers to allow requests from your React application's origin while blocking requests from unauthorized origins.
* **Don't Do This:** Enable wildcard CORS ("Access-Control-Allow-Origin: *") in production environments.
**Why:** Protects your API from unauthorized access by restricting cross-origin requests.
## 5. Performance Optimization
### 5.1 Memoization
**Standard:** Use memoization techniques to prevent unnecessary API calls.
* **Do This:** Utilize "React.memo" or "useMemo" hook to memoize components and values that depend on API data.
* **Don't Do This:** Fetch the same data repeatedly in different components without caching or memoization.
**Why:** Reduces the number of API requests, improves performance, and decreases the load on the server.
**Code Example:**
"""javascript
import React, { useState, useEffect, useMemo } from 'react';
import apiService from './apiService';
function MyComponent({ userId }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const result = await apiService.getUser(userId);
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [userId]);
//Memoize the data so that only changes to data re-render children that use memo.
//If data isn't used directly in this component, this is not necessary.
const memoizedData = useMemo(() => data, [data]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
if (!memoizedData) return <p>No data</p>;
return (
{/*Render Data*/}
);
}
export default React.memo(MyComponent); // Memoize the entire component
"""
### 5.2 Pagination and Lazy Loading
**Standard:** Implement pagination and lazy loading for large datasets.
* **Do This:** Use server-side pagination and virtualized lists to load data in chunks as the user scrolls.
* **Don't Do This:** Load entire datasets at once, which can lead to performance issues and a poor user experience.
**Why:** Reduces initial load time, improves responsiveness, and minimizes memory usage.
### 5.3 Code Splitting
**Standard:** Use code splitting to load only the necessary code for each route or component.
* **Do This:** Utilize React.lazy and Suspense to load components asynchronously.
* **Don't Do This:** Load all your code in a single bundle, which can lead to slow initial load times.
**Why:** Improves initial load time, reduces the amount of code that needs to be parsed and executed, and enhances the overall performance of the application.
By adhering to these standards, React developers can build more maintainable, secure, and performant applications that effectively integrate with backend services and external APIs. Regular review and updates to these guidelines are essential to adapt to the evolving React ecosystem and emerging best practices.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Component Design Standards for React This document outlines the component design standards for React applications. Following these standards will lead to more maintainable, reusable, and performant React code. These guidelines are designed to align with the latest React features and best practices. ## 1. Component Architecture ### 1.1 Component Types * **Standard:** Differentiate between container (smart/stateful) and presentational (dumb/stateless) components using the Context API and Hooks. Favor functional components over class components. * **Do This:** Create functional components using arrow function syntax and hooks, such as "useState", "useEffect", and "useContext". * **Don't Do This:** Use class components unless there is a compelling reason to (e.g., integrating with legacy class-based code). * **Why:** Functional components with hooks are more concise, easier to test, and promote better separation of concerns. * **Code Example:** """jsx // Presentational Component (MyComponent.jsx) import React from 'react'; const MyComponent = ({ data, onClick }) => { return ( <div onClick={onClick}> {data.name} </div> ); }; export default MyComponent; // Container Component (MyContainer.jsx) import React, { useState } from 'react'; import MyComponent from './MyComponent'; const MyContainer = () => { const [myData, setMyData] = useState({ name: 'Initial Data' }); const handleClick = () => { setMyData({ name: 'Updated Data' }); }; return ( <MyComponent data={myData} onClick={handleClick} /> ); }; export default MyContainer; """ ### 1.2 Folder Structure * **Standard:** Organize components into a feature-based directory structure. Place component-related files (component, tests, styles) within the same directory. * **Do This:** """ src/ └── components/ └── MyFeature/ ├── MyFeature.jsx ├── MyFeature.test.jsx └── MyFeature.styles.js """ * **Don't Do This:** """ src/ ├── components/ │ ├── MyFeature.jsx ├── tests/ │ ├── MyFeature.test.jsx ├── styles/ │ ├── MyFeature.styles.js """ * **Why:** This structure improves discoverability and maintainability by grouping related code together. * **Standard:** Use an "index.js" file within each component directory for easier imports. * **Do This:** """javascript // src/components/MyFeature/index.js export { default } from './MyFeature'; """ """jsx // Import statement import MyFeature from './components/MyFeature'; """ * **Why:** Simplifies import statements and prevents long, repetitive import paths. ### 1.3 Component Composition * **Standard:** Favor component composition over inheritance. Use the "children" prop to pass content to components. * **Do This:** """jsx // Layout Component const Layout = ({ children }) => { return ( <div className="layout"> <header>Header</header> <main>{children}</main> <footer>Footer</footer> </div> ); }; // Usage <Layout> <p>Content inside the layout</p> </Layout> """ * **Don't Do This:** Using inheritance patterns that are not idiomatic in React. * **Why:** Composition promotes reusability and avoids the complexities of inheritance, leading to simpler, more predictable code. ## 2. Component Implementation ### 2.1 Props * **Standard:** Use PropTypes or TypeScript for type-checking props. This allows you to catch errors early in development. When appropriate, use TypeScript. * **Do This (PropTypes):** """jsx import React from 'react'; import PropTypes from 'prop-types'; const MyComponent = ({ name, age }) => { return ( <div> {name} is {age} years old. </div> ); }; MyComponent.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number }; export default MyComponent; """ * **Do This (TypeScript):** """tsx import React from 'react'; interface MyComponentProps { name: string; age?: number; // Optional } const MyComponent: React.FC<MyComponentProps> = ({ name, age }) => { return ( <div> {name} is {age} years old. </div> ); }; export default MyComponent; """ * **Don't Do This:** Skipping prop type validation, especially in larger projects. * **Why:** Type-checking improves code reliability and helps prevent runtime errors. PropTypes provide runtime validations, while TypeScript provides static type checking during development. * **Standard:** Destructure props in functional components for readability. * **Do This:** """jsx const MyComponent = ({ name, age }) => { return ( <div> {name} is {age} years old. </div> ); }; """ * **Don't Do This:** """jsx const MyComponent = (props) => { return ( <div> {props.name} is {props.age} years old. </div> ); }; """ * **Why:** Destructuring makes your code cleaner and easier to understand. * **Standard:** Spread props when passing them down to child components, but only when appropriate (e.g., when creating a wrapper component that passes all props to a specific child component). Be mindful of potential prop collisions. * **Do This:** """jsx const ParentComponent = (props) => { return <ChildComponent {...props} />; }; """ However, it's generally better practice to explicitly declare the props being passed: """jsx const ParentComponent = ({ name, age, otherProp }) => { return <ChildComponent name={name} age={age} otherProp={otherProp} />; }; """ * **Don't Do This:** Blindly spreading props without understanding what they are doing. * **Why:** Spreading props can reduce boilerplate, but it can also make your code harder to understand and debug if not used carefully. Explicitly pass props to increase readability and maintainability. ### 2.2 State Management * **Standard:** Use React's "useState" hook for local component state. For more complex state management, consider using Context API, Redux, Zustand, or similar state management libraries. * **Do This ("useState"):** """jsx import React, { useState } from 'react'; const Counter = () => { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default Counter; """ * **Do This (Context API):** """jsx // Context Creation import React, { createContext, useState, useContext } from 'react'; const MyContext = createContext(); const MyProvider = ({ children }) => { const [value, setValue] = useState('Initial Value'); return ( <MyContext.Provider value={{ value, setValue }}> {children} </MyContext.Provider> ); }; // Hook to use Context const useMyContext = () => { return useContext(MyContext); }; export { MyProvider, useMyContext }; // Usage import React from 'react'; import { useMyContext } from './MyContext'; const MyComponent = () => { const { value, setValue } = useMyContext(); return ( <div> <p>Value: {value}</p> <button onClick={() => setValue('New Value')}>Update</button> </div> ); }; export default MyComponent; """ * **Don't Do This:** Using "setState" directly in class components when hooks are more appropriate. Overusing global state management for component-local state. Mutating state directly (always use the setter function) unless you know what you’re doing. * **Why:** "useState" is simple and effective for managing local state. Context API provides a way to share state between components without prop drilling. Libraries like Redux provide a centralized store for managing application-wide state in a predictable way. Zustand offers a simpler alternative to Redux. ### 2.3 Effects * **Standard:** Use the "useEffect" hook for performing side effects in functional components (data fetching, subscriptions, manual DOM manipulations). * **Do This:** """jsx import React, { useState, useEffect } from 'react'; const DataFetcher = ({ url }) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { const response = await fetch(url); const result = await response.json(); setData(result); } catch (err) { setError(err); } finally { setLoading(false); } }; fetchData(); }, [url]); // Dependency array is crucial! if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error.message}</p>; if (!data) return <p>No data</p>; return ( <div> {Object.keys(data).map(key => ( <p key={key}>{key}: {data[key]}</p> ))} </div> ); }; export default DataFetcher; """ * **Don't Do This:** Neglecting the dependency array in "useEffect", leading to infinite loops or stale closures. Performing synchronous, long-running tasks within "useEffect" that block the UI thread. Omitting necessary dependencies from the dependency array, which can cause your effect to not run when it should. * **Why:** "useEffect" manages side effects in a controlled manner. The dependency array ensures that effects only run when necessary. * **Standard:** Clean up effects to prevent memory leaks (e.g., unsubscribe from subscriptions, cancel timers). * **Do This:** """jsx import React, { useState, useEffect } from 'react'; const MyComponent = () => { const [count, setCount] = useState(0); useEffect(() => { const intervalId = setInterval(() => { setCount(prevCount => prevCount + 1); }, 1000); return () => { clearInterval(intervalId); // Clean up the interval }; }, []); return ( <div> <p>Count: {count}</p> </div> ); }; export default MyComponent; """ * **Why:** Failing to clean up effects can lead to memory leaks and performance issues. ### 2.4 Context * **Standard:** Use "useContext" to access context values in functional components. Create custom hooks to provide a more convenient API for accessing context. * **Do This:** (See example above in State Management) * **Why:** Simplifies access to context values and promotes code reusability. ### 2.5 React Fragments * **Standard:** Use React Fragments ("<> </>" or "<React.Fragment>") to avoid adding unnecessary DOM nodes. * **Do This:** """jsx import React from 'react'; const MyComponent = () => { return ( <> <h1>Title</h1> <p>Content</p> </> ); }; export default MyComponent; """ * **Don't Do This:** Returning multiple elements without wrapping them in a Fragment or a DOM element. * **Why:** Fragments reduce the amount of unnecessary HTML, leading to better performance and cleaner DOM structure. ### 2.6 Keyed Fragments * **Standard:** When mapping over a list of items and returning fragments set the "key" prop on the Fragment. * **Do This:** """jsx import React from 'react'; const MyComponent = ({ items }) => { return ( <> {items.map(item => ( <React.Fragment key={item.id}> <p>{item.name}</p> </React.Fragment> ))} </> ); }; export default MyComponent; """ * **Why:** React needs the key to manage and efficiently update the list of items. In this case, since the *direct* return of the map function value is a fragment, the key must be applied directly to the React.Fragment. ### 2.7 Error Boundaries * **Standard:** Implement error boundaries to catch JavaScript errors anywhere in the their child component tree, log those errors, and display a fallback UI. * **Do This:** """jsx import React, { Component } from 'react'; class ErrorBoundary extends Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI. return { hasError: true }; } componentDidCatch(error, errorInfo) { // You can also log the error to an error reporting service console.error(error, errorInfo); } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>Something went wrong.</h1>; } return this.props.children; } } export default ErrorBoundary; // Usage <ErrorBoundary> <MyComponent /> </ErrorBoundary> """ * **Don't Do This:** Assuming that React code is always error-free, especially when dealing with external data or user input. * **Why:** Error boundaries prevent the entire application from crashing due to a single component error and provide a better user experience. ### 2.8 Forwarding Refs * **Standard:** Use "React.forwardRef" when you need to access the DOM node of a child component from a parent component. * **Do This:** """jsx import React, { forwardRef } from 'react'; const MyInput = forwardRef((props, ref) => { return ( <input type="text" ref={ref} {...props} /> ); }); // Usage import React, { useRef } from 'react'; import MyInput from './MyInput'; const ParentComponent = () => { const inputRef = useRef(null); const handleClick = () => { inputRef.current.focus(); }; return ( <div> <MyInput ref={inputRef} /> <button onClick={handleClick}>Focus Input</button> </div> ); }; """ * **Don't Do This:** Directly accessing DOM nodes without using refs or when it isn't necessary. * **Why:** Forwarding refs allows you to access and manipulate DOM nodes of child components when needed. ### 2.9 Memoization * **Standard:** Use "React.memo" for functional components to prevent unnecessary re-renders. Use "useMemo" and "useCallback" hooks for optimizing performance within components. * **Do This ("React.memo"):** """jsx import React from 'react'; const MyComponent = ({ data }) => { console.log('MyComponent rendered'); return ( <div>{data.name}</div> ); }; export default React.memo(MyComponent); """ * **Do This ("useMemo" and "useCallback"):** """jsx import React, { useState, useCallback, useMemo } from 'react'; const MyComponent = () => { const [count, setCount] = useState(0); const expensiveCalculation = useMemo(() => { console.log('Calculating...'); let result = 0; for (let i = 0; i < 1000000; i++) { result += i; } return result; }, [count]); const increment = useCallback(() => { setCount(c => c + 1); }, []); return ( <div> <p>Count: {count}</p> <p>Expensive Calculation: {expensiveCalculation}</p> <button onClick={increment}>Increment</button> </div> ); }; export default MyComponent; """ * **Don't Do This:** Overusing memoization without profiling, as it can add overhead. Creating new functions or objects inline in render methods, as this will defeat memoization efforts. * **Why:** Memoization can significantly improve performance by preventing unnecessary re-renders and re-calculations. "useCallBack" prevents functions passed as props from being recreated on every render (which would break "React.memo"'s shallow prop comparison.) ## 3. Code Style and Formatting ### 3.1 Consistent Formatting * **Standard:** Use a code formatter like Prettier and configure it to automatically format code on save. * **Why:** Consistent formatting improves code readability and reduces cognitive load. ### 3.2 ESLint Configuration * **Standard:** Use ESLint with a well-defined configuration (e.g., Airbnb, Standard) to enforce consistent coding style and catch potential errors. * **Why:** ESLint helps maintain code quality and consistency across the project. ### 3.3 Comments * **Standard:** Write clear and concise comments to explain complex logic or non-obvious code. * **Why:** Comments improve code understandability, especially for maintainers who are not familiar with the codebase. ## 4. Testing ### 4.1 Unit Tests * **Standard:** Write unit tests for all components to ensure they behave as expected. Use testing libraries like Jest and React Testing Library. * **Why:** Unit tests improve code reliability and make it easier to refactor code without introducing regressions. * **Example:** """jsx // MyComponent.test.jsx import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import MyComponent from './MyComponent'; test('renders the component with the correct name', () => { render(<MyComponent name="Test Name" />); const nameElement = screen.getByText('Test Name'); expect(nameElement).toBeInTheDocument(); }); test('calls the onClick handler when the button is clicked', () => { const handleClick = jest.fn(); render(<MyComponent name="Test Name" onClick={handleClick} />); const button = screen.getByRole('button'); fireEvent.click(button); expect(handleClick).toHaveBeenCalledTimes(1); }); """ ### 4.2 Integration Tests * **Standard:** Write integration tests to ensure that components work together correctly. * **Why:** Integration tests verify that the different parts of the application are properly integrated. ### 4.3 End-to-End Tests * **Standard:** Use end-to-end testing frameworks like Cypress or Selenium to test the application from the user's perspective. * **Why:** End-to-end tests ensure that the application works correctly in a real-world environment. ## 5. Performance Optimization ### 5.1 Code Splitting * **Standard:** Implement code splitting to reduce the initial load time of the application. Use "React.lazy" and "Suspense" for dynamic imports. * **Do This:** """jsx import React, { Suspense } from 'react'; const MyComponent = React.lazy(() => import('./MyComponent')); const App = () => { return ( <Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense> ); }; export default App; """ * **Why:** Code splitting allows you to load code on demand, reducing the initial bundle size and improving performance. ### 5.2 Image Optimization * **Standard:** Optimize images by compressing them, using appropriate formats (WebP, JPEG, PNG), and using lazy loading. * **Why:** Optimized images reduce the amount of data that needs to be transferred, improving page load times. ### 5.3 Virtualization * **Standard:** Use virtualization libraries like "react-window" or "react-virtualized" for rendering large lists of data. * **Why:** Virtualization improves performance by only rendering the visible items in the list, reducing the amount of DOM nodes. ## 6. Accessibility (A11y) ### 6.1 Semantic HTML * **Standard:** Use semantic HTML elements (e.g., "<header>", "<nav>", "<article>", "<aside>", "<footer>") to provide structure and meaning to the content. * **Why:** Semantic HTML improves accessibility by providing context to assistive technologies such as screen readers. ### 6.2 ARIA Attributes * **Standard:** Use ARIA attributes to provide additional information about UI elements for assistive technologies. * **Why:** ARIA attributes can improve accessibility for users with disabilities by providing more context about the purpose and state of UI elements. ### 6.3 Keyboard Navigation * **Standard:** Ensure that all interactive elements are accessible via keyboard navigation. Use the "tabIndex" attribute and proper focus management. * **Why:** Keyboard navigation is essential for users who cannot use a mouse or other pointing device. ### 6.4 Color Contrast * **Standard:** Use sufficient color contrast between text and background to ensure readability for users with visual impairments. * **Why:** Adequate color contrast is essential for readability and accessibility. Use tools like WebAIM's Color Contrast Checker to ensure compliance. By adhering to these component design standards, development teams can build more robust, maintainable, and performant React applications. These guidelines provide a solid foundation for creating high-quality code that meets the needs of both developers and users.
# Tooling and Ecosystem Standards for React This document outlines the recommended tooling and ecosystem standards for React development, ensuring consistency, maintainability, and performance across projects. It serves to guide developers and inform AI coding assistants like GitHub Copilot, Cursor, and similar tools. ## 1. Development Environment Setup ### 1.1. Project Structure (Create React App or Similar Alternatives) **Do This:** Use a modern project setup tool such as Create React App (CRA), Vite, or Next.js to bootstrap your React projects. These tools provide sensible defaults and streamline the setup process. **Don't Do This:** Manually configure Webpack, Babel, and other build tools from scratch unless absolutely necessary. This introduces unnecessary complexity and maintenance overhead. **Why:** Modern project setup tools abstract away much of the complexity of configuring a React development environment. They come with pre-configured build pipelines, hot reloading, and other features that improve developer productivity. They also automatically keeps dependencies up-to-date. **Example (using Create React App):** """bash npx create-react-app my-app cd my-app npm start """ **Alternative (using Vite):** """bash npm create vite@latest my-vite-app --template react cd my-vite-app npm install npm run dev """ ### 1.2. Code Editor and IDE **Do This:** Use a code editor or IDE with strong React support, such as Visual Studio Code (VS Code) with appropriate extensions, or WebStorm. **Don't Do This:** Use a basic text editor without syntax highlighting, linting, or debugging support. **Why:** A good code editor or IDE drastically improves developer productivity through features like syntax highlighting, code completion, linting, debugging, and refactoring. **Recommended VS Code Extensions:** * **ESLint:** For identifying and fixing JavaScript and React code style issues. * **Prettier:** For automatically formatting code according to a consistent style. * **React Developer Tools:** For debugging React components directly in the browser. * **Simple React Snippets:** Enhances the speed of development with React snippets. * **npm Intellisense:** Autocompletes npm modules in import statements. * **EditorConfig for VS Code:** Maintains consistent coding styles across different editors and IDE's. **Example Configuration (.vscode/settings.json):** """json { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "eslint.validate": [ "javascript", "javascriptreact", "typescript", "typescriptreact" ], "files.eol": "\n", "files.insertFinalNewline": true } """ ### 1.3. Browser Developer Tools **Do This:** Utilize the React Developer Tools browser extension (available for Chrome, Firefox, and Edge). **Don't Do This:** Rely solely on "console.log" statements for debugging complex React components. **Why:** React Developer Tools provides a powerful and intuitive way to inspect component hierarchies, props, state, and performance metrics. **Key Features of React Developer Tools:** * **Component Inspection:** Inspect the component tree, view props and state. * **Profiler:** Identify performance bottlenecks and optimize rendering. * **Highlight Updates:** Highlight components that are re-rendering. ## 2. Linting and Formatting ### 2.1. ESLint **Do This:** Configure ESLint with a React-specific configuration (e.g., "eslint-config-react-app" from Create React App) and enforce strict linting rules. **Don't Do This:** Ignore ESLint warnings or disable important rules without a valid justification. **Why:** ESLint helps identify potential errors, enforce coding standards, and improve code quality. **Example ESLint Configuration (.eslintrc.js):** """javascript module.exports = { extends: ['react-app', 'react-app/jest', 'eslint:recommended', 'plugin:react/recommended'], rules: { 'no-unused-vars': 'warn', 'react/prop-types': 'off', // Use TypeScript for PropTypes 'react/react-in-jsx-scope': 'off', // Not required for React 17+ 'eqeqeq': 'warn', 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off' }, parserOptions: { ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true } }, env: { browser: true, es2021: true, node: true } }; """ ### 2.2. Prettier **Do This:** Integrate Prettier into your workflow (e.g., using a VS Code extension or Husky hooks) to automatically format code on save. **Don't Do This:** Manually format code or allow inconsistent formatting across the codebase. **Why:** Prettier ensures code formatting consistent across your project. **Example Prettier Configuration (.prettierrc.js):** """javascript module.exports = { semi: true, // Add semicolons at the end of statements trailingComma: 'all', // Add trailing commas in multi-line arrays, objects, etc. singleQuote: true, // Use single quotes instead of double quotes printWidth: 120, // Maximum line length tabWidth: 2, // Number of spaces per indentation level }; """ ### 2.3. Husky and Lint-Staged **Do This:** Use Husky and lint-staged to run linters and formatters on staged files before committing. **Don't Do This:** Commit code that violates linting rules or is not properly formatted. **Why:** Husky and lint-staged prevent bad code from being committed to the repository. **Example Husky Configuration (package.json):** """json { "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "*.{js,jsx,ts,tsx,json,md}": [ "prettier --write", "eslint --fix" ] } } """ ## 3. State Management Libraries ### 3.1. Choosing a State Management Library **Do This:** Carefully evaluate your application's complexity and choose a state management solution (e.g., Context API with reducers, Redux Toolkit, Zustand, Jotai, Recoil) that is appropriate for your needs. Start with the Context API for simple cases, and adopt a dedicated library for more complex scenarios. **Don't Do This:** Automatically reach for Redux for every project, even if it's unnecessary. Overusing complex state management can lead to code bloat and performance issues. **Why:** Proper state management is essential for building scalable and maintainable React applications. **Guidelines for Choosing a State Management Library:** * **Context API with "useReducer":** Suitable for small to medium-sized applications with localized state. * **Redux Toolkit:** Best for complex applications with global state and complex data flow. Provides a simplified Redux experience. * **Zustand:** Minimalistic barebones state management with a very simple API. Consider for cases when simpler state handling is desired. * **Jotai/Recoil:** Atomic state management libraries that offer fine-grained control over state updates and efficient re-renders. Consider for performance critical applictions. ### 3.2. Redux Toolkit **Do This:** Utilize Redux Toolkit for managing complex global state in larger applications. **Don't Do This:** Use the original Redux boilerplate without Redux Toolkit's utilities, because the boilerplate code is too verbose. **Why:** Redux Toolkit simplifies Redux development and reduces boilerplate code. **Example Redux Toolkit Configuration (store.js):** """javascript import { configureStore } from '@reduxjs/toolkit'; import counterReducer from '../features/counter/counterSlice'; export const store = configureStore({ reducer: { counter: counterReducer, }, }); export default store; """ **Example Redux Toolkit Slice (counterSlice.js):** """javascript import { createSlice } from '@reduxjs/toolkit'; const initialState = { value: 0, }; export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; }, }, }); export const { increment, decrement, incrementByAmount } = counterSlice.actions; export const selectCount = (state) => state.counter.value; export default counterSlice.reducer; """ ### 3.3. State Management Anti-Patterns **Avoid:** * **Prop drilling:** Passing props through multiple levels of the component tree unnecessarily. * **Global state overuse:** Storing data in global state that is only needed by a few components. * **Mutating state directly:** Always create new state objects to trigger re-renders correctly. ## 4. Component Libraries and UI Frameworks ### 4.1. Choosing a Component Library **Do This:** If your project requires a consistent and visually appealing UI, consider using a component library such as Material UI, Ant Design, Chakra UI, or Tailwind CSS with custom components. **Don't Do This:** Re-invent the wheel by building basic UI components from scratch. **Why:** Component libraries provide pre-built, accessible, and customizable UI components that accelerate development. **Common Component Libraries:** * **Material UI:** Implements Google's Material Design. * **Ant Design:** Offers enterprise-grade UI components with a polished aesthetic. * **Chakra UI:** Provides a set of accessible and customizable UI components with a focus on developer experience. * **Tailwind CSS:** A utility-first CSS framework that allows you to build custom designs quickly. ### 4.2. Using Component Libraries Effectively **Do This:** Customize component libraries to align with your project's design and branding. Use them as building blocks to create more complex, domain-specific components. **Don't Do This:** Rely solely on the default styling of component libraries without customization. **Example (using Material UI):** """javascript import { Button, createTheme, ThemeProvider } from '@mui/material'; const theme = createTheme({ palette: { primary: { main: '#007bff', // Custom primary color }, }, }); function MyComponent() { return ( <ThemeProvider theme={theme}> <Button variant="contained" color="primary"> Click Me </Button> </ThemeProvider> ); } export default MyComponent; """ ### 4.3. UI Framework Considerations (Tailwind CSS) **Do This**: Use Tailwind CSS with a component approach. Create React components that encapsulate the Tailwind classes. **Don't Do This**: Apply inline Tailwind classes directly to the JSX elements in a complex component. This creates unreadable markup. **Why**: Encapsulating Tailwind classes increases reusability and makes the markup easier to read and maintain. """jsx // Good: Component Approach function Button({ children }) { return ( <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> {children} </button> ); } // Then use it like this: <Button>Click Me</Button> // Bad: Inline Classes <div> {/* Hard to read and not reusable */} <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Click Me</button> </div> """ ## 5. API Communication ### 5.1. Fetch API and Axios **Do This:** Use the built-in "fetch" API or a library like Axios for making API requests. Use "async/await" for cleaner asynchronous code. **Don't Do This:** Rely on outdated methods like "XMLHttpRequest". **Why:** "fetch" and Axios provide a modern and flexible way to interact with APIs. **Example (using "fetch" with "async/await"):** """javascript async function fetchData() { try { const response = await fetch('/api/data'); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } const data = await response.json(); console.log(data); return data; } catch (error) { console.error('Error fetching data:', error); } } """ **Example (using Axios with "async/await"):** """javascript import axios from 'axios'; async function fetchData() { try { const response = await axios.get('/api/data'); console.log(response.data); return response.data; } catch (error) { console.error('Error fetching data:', error); } } """ ### 5.2. React Query or SWR **Do This:** Utilize React Query or SWR for managing server state, caching, and data fetching. **Don't Do This:** Manually implement caching and re-fetching logic. **Why:** React Query and SWR simplify server state management and improve application performance. **Example (using React Query):** """javascript import { useQuery } from 'react-query'; async function fetchData() { const response = await fetch('/api/data'); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } return await response.json(); } function MyComponent() { const { data, isLoading, error } = useQuery('myData', fetchData); if (isLoading) return <p>Loading...</p>; if (error) return <p>Error: {error.message}</p>; return ( <div> {data.map(item => ( <p key={item.id}>{item.name}</p> ))} </div> ); } """ ### 5.3. Error Handling **Do This:** Implement robust error handling for API requests. Use try-catch blocks, handle network errors, and display user-friendly error messages. **Don't Do This:** Silently ignore API errors or display generic error messages. **Why:** Proper error handling improves the user experience and helps identify and resolve issues quickly. ## 6. Testing ### 6.1. Testing Frameworks **Do This:** Use a testing framework like Jest with React Testing Library for unit and integration testing. **Don't Do This:** Neglect writing tests or rely solely on manual testing. **Why:** Automated testing ensures code quality and helps prevent regressions. **Example Jest and React Testing Library Test:** """javascript import { render, screen } from '@testing-library/react'; import MyComponent from './MyComponent'; test('renders learn react link', () => { render(<MyComponent />); const linkElement = screen.getByText(/learn react/i); expect(linkElement).toBeInTheDocument(); }); """ ### 6.2. Test-Driven Development (TDD) **Consider:** Adopting a TDD approach for new features and components. **Why:** TDD helps ensure that code meets requirements and avoids bugs early in the development process. ### 6.3. End-to-End Testing **Do This:** Use end-to-end (E2E) testing frameworks like Cypress or Playwright to test the application holistically. **Don't Do This:** Skip E2E tests for critical user flows. **Why:** E2E tests verify that the entire application works correctly from the user's perspective. ## 7. Performance Optimization ### 7.1. Code Splitting **Do This:** Implement code splitting using React.lazy and dynamic imports to reduce the initial bundle size. **Don't Do This:** Load the entire application code upfront. **Why:** Code splitting improves initial load time by loading only the code that is needed for the current view. **Example (using "React.lazy"):** """javascript import React, { Suspense } from 'react'; const MyComponent = React.lazy(() => import('./MyComponent')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense> ); } """ ### 7.2. Memoization **Do This:** Use "React.memo", "useMemo", and "useCallback" to memoize components and expensive calculations. **Don't Do This:** Overuse memoization, as it can add unnecessary complexity and overhead. Only memoize components and calculations that are known to be performance bottlenecks. **Why:** Memoization can improve performance by preventing unnecessary re-renders. **Example (using "React.memo"):** """javascript import React from 'react'; const MyComponent = React.memo(({ data }) => { console.log('MyComponent rendered'); return <div>{data.value}</div>; }); export default MyComponent; """ **Example (using "useMemo"):** """javascript import React, { useMemo } from 'react'; function calculateExpensiveValue(data) { console.log('Calculating expensive value'); // Perform expensive calculation here return data.value * 2; } function MyComponent({ data }) { const expensiveValue = useMemo(() => calculateExpensiveValue(data), [data]); return <div>{expensiveValue}</div>; } """ ### 7.3. Virtualization **Do This:** Use virtualization libraries for rendering large lists of data. **Don't Do This:** Render all list items at once, as this can cause performance issues. **Why:** Virtualization only renders the visible items, improving scrolling performance. **Example (using "react-virtualized"):** """javascript import { List } from 'react-virtualized'; function MyComponent({ data }) { function rowRenderer({ index, key, style }) { return ( <div key={key} style={style}> {data[index].name} </div> ); } return ( <List width={300} height={300} rowCount={data.length} rowHeight={20} rowRenderer={rowRenderer} /> ); } """ ### 7.4. Image Optimization **Do This**: Optimize images by compressing them. Use a CDN or next/image for advanced optimization techniques like lazy loading images, optimizing for different devices. **Don't Do This**: Serve large uncompressed images directly. **Why**: Optimized images improve page load times and reduce bandwidth consumption. ## 8. Accessibility (a11y) ### 8.1. Semantic HTML **Do This:** Use semantic HTML elements (e.g., "<header>", "<nav>", "<article>", "<aside>", "<footer>") to structure your React components. **Don't Do This:** Rely solely on "<div>" and "<span>" elements for structuring content. **Why:** Semantic HTML improves accessibility by providing clear meaning to assistive technologies. ### 8.2. ARIA Attributes **Do This:** Use ARIA attributes to provide additional information about interactive elements for assistive technologies. **Don't Do This:** Overuse ARIA attributes or use them incorrectly. **Why:** ARIA attributes enhance the accessibility of dynamic content. ### 8.3. Keyboard Navigation **Do This:** Ensure that all interactive elements are focusable and can be navigated using the keyboard. **Don't Do This:** Create focus traps or make it difficult to navigate the application. **Why:** Keyboard navigation is essential for users who cannot use a mouse. ### 8.4. Color Contrast **Do This:** Ensure sufficient color contrast between text and background colors. **Don't Do This:** Use color combinations that are difficult to read for users with visual impairments. **Why:** Adequate color contrast improves readability and accessibility. ### 8.5. Screen Reader Testing **Do This:** Test your application with a screen reader like NVDA or VoiceOver. **Don't Do This:** Assume that your application is accessible without testing it with assistive technologies. **Why:** Screen reader testing helps identify accessibility issues that may not be apparent otherwise. ## 9. Deployment ### 9.1. Hosting Platforms **Do This:** Deploy your React application to a reliable hosting platform like Netlify, Vercel, AWS Amplify, or Firebase Hosting. **Don't Do This:** Host your application on a server without proper security and scaling considerations. **Why:** Modern hosting platforms offer features like automatic deployments, CDN integration, and scalability. ### 9.2. Continuous Integration/Continuous Deployment (CI/CD) **Do This:** Set up a CI/CD pipeline to automate testing and deployment. **Don't Do This:** Manually deploy code to production. **Why:** CI/CD pipelines streamline the deployment process and reduce the risk of errors. ### 9.3. Environment Variables **Do This:** Manage environment-specific configuration using environment variables, ideally managed by your hosting provider. **Don't Do This:** Hardcode sensitive information or configuration directly in your code. **Why:** Isolates code from environment configuration, improving maintainability and security. This comprehensive set of tooling and ecosystem standards for React development will ensure a systematic approach to building robust, scalable, performant, accessibleReact applications. Incorporating these concepts into standard development practices, and leveraging generative AI models, will ensure long-term success.
# Core Architecture Standards for React This document outlines the core architectural standards for building React applications. It focuses on fundamental patterns, project structure, and organization principles to ensure maintainability, scalability, and performance. These standards are aligned with the latest version of React and incorporate modern best practices. ## 1. Architectural Patterns Choosing the right architectural pattern is crucial for the long-term success of a React project. This section describes the preferred architectural patterns and provides guidance on when to use them. ### 1.1. Component-Based Architecture React is fundamentally component-based. Components are independent, reusable building blocks that manage their own state and render UI. **Do This:** * Design UIs as a composition of small, focused, and reusable components. * Favor composition over inheritance. * Abstract complex logic into custom hooks. * Use prop types or TypeScript for type checking. **Don't Do This:** * Create monolithic components that handle too many responsibilities. * Rely heavily on inheritance for code reuse. * Mix UI rendering logic with complex business logic within a single component. **Why:** * **Maintainability:** Smaller components are easier to understand, test, and modify. * **Reusability:** Components can be reused across different parts of the application, reducing code duplication. * **Testability:** Focused components are easier to test in isolation. **Example:** """jsx // Good: A simple, reusable button component import React from 'react'; import PropTypes from 'prop-types'; const Button = ({ children, onClick, className }) => { return ( <button className={"button ${className}"} onClick={onClick}> {children} </button> ); }; Button.propTypes = { children: PropTypes.node.isRequired, onClick: PropTypes.func, className: PropTypes.string, }; export default Button; // Bad: A button component with too much logic import React from 'react'; const BadButton = ({ text, onClick, isLoading }) => { if (isLoading) { return <button disabled>Loading...</button>; // Logic within the component } return <button onClick={onClick}>{text}</button>; }; """ ### 1.2. Container/Presentational (Smart/Dumb) Components This pattern separates components into two categories: * **Container Components:** Handle data fetching, state management, and logic. They may use hooks or other techniques to fulfill that purpose. * **Presentational Components:** Focus solely on rendering UI based on props. These are purely functional components that depend only on their inputs. **Do This:** * Create container components to fetch data and manage state. * Pass data and callbacks to presentational components via props. * Keep presentational components as simple and focused as possible. * Connect container components to state management libraries like Redux or Zustand, if needed. **Don't Do This:** * Mix data fetching or state management logic directly within presentational components. * Introduce side effects within presentational components. **Why:** * **Separation of Concerns:** Clearly separates UI rendering from application logic. * **Testability:** Presentational components are easier to test as they are purely functional. * **Reusability:** Presentational components can be reused with different data sources. **Example:** """jsx // Presentational Component (Stateless) import React from 'react'; import PropTypes from 'prop-types'; const UserProfile = ({ name, email }) => ( <div> <h2>{name}</h2> <p>{email}</p> </div> ); UserProfile.propTypes = { name: PropTypes.string.isRequired, email: PropTypes.string.isRequired, }; export default UserProfile; // Container Component (Stateful) import React, { useState, useEffect } from 'react'; import UserProfile from './UserProfile'; const UserProfileContainer = () => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { // Simulate fetching user data setTimeout(() => { setUser({ name: 'John Doe', email: 'john.doe@example.com' }); setLoading(false); }, 1000); }, []); if (loading) { return <p>Loading user profile...</p>; } return <UserProfile name={user.name} email={user.email} />; }; export default UserProfileContainer; """ ### 1.3. Hooks-Based Architecture React Hooks allow you to use state and other React features in functional components. This promotes cleaner, more reusable code. The best practice is now to use the "use" hook and React Server Components for data fetching. **Do This:** * Use built-in hooks like "useState", "useEffect", "useContext", "useRef", "useMemo", and "useCallback". * Create custom hooks to extract reusable logic. * Follow the "Rules of Hooks": only call Hooks at the top level of a function component or custom Hook, and only call Hooks from React function components or custom Hooks. * Consider React Server Components for data fetching and backend integration. **Don't Do This:** * Rely on class components for new development. * Implement complex logic directly within the component body without extraction. * Ignore linting rules related to hooks (e.g., "eslint-plugin-react-hooks"). **Why:** * **Code Reusability:** Custom hooks promote code reuse and reduce duplication. * **Readability:** Hooks make component logic easier to understand and maintain. * **Testability:** Hooks can be tested independently. **Example:** """jsx // Custom Hook for Tracking Window Size import { useState, useEffect } from 'react'; function useWindowSize() { const [windowSize, setWindowSize] = useState({ width: window.innerWidth, height: window.innerHeight, }); useEffect(() => { function handleResize() { setWindowSize({ width: window.innerWidth, height: window.innerHeight, }); } window.addEventListener('resize', handleResize); handleResize(); // Call handler right away so state gets updated with initial window size return () => window.removeEventListener('resize', handleResize); }, []); // Empty array ensures that effect is only run on mount and unmount return windowSize; } export default useWindowSize; // Usage in a Component import React from 'react'; import useWindowSize from './useWindowSize'; const MyComponent = () => { const { width, height } = useWindowSize(); return ( <div> <p>Window Width: {width}px</p> <p>Window Height: {height}px</p> </div> ); }; export default MyComponent; """ ### 1.4. React Server Components (RSCs) React Server Components (RSCs) allow you to render components on the server, improving performance and SEO. **Do This:** * Use RSCs for data fetching and backend integration. * Use the "use" hook in RSCs for data fetching. * Understand the limitations of RSCs, such as the inability to use client-side interactivity directly. **Don't Do This:** * Use RSCs for components that require client-side state or interactivity unless combined cautiously with client components. **Why:** * **Performance:** Rendering components on the server reduces the amount of JavaScript that needs to be downloaded and executed on the client. * **SEO:** Server-rendered content is easier for search engines to crawl. **Example:** """jsx // Server Component (app/components/Greeting.js) import { use } from 'react'; async function getData() { const res = await fetch('https://jsonplaceholder.typicode.com/todos/1'); // Recommendation: handle errors if (!res.ok) { // This will activate the closest "error.js" Error Boundary throw new Error('Failed to fetch data') } return res.json() } export default function Greeting() { const data = use(getData()); return <h1>Hello, {data.title}</h1>; } """ ## 2. Project Structure and Organization A well-defined project structure is essential for maintainability and collaboration. This section outlines a recommended project structure for React applications. ### 2.1. Feature-Based Structure Organize your project around features rather than technology. Each feature should reside in its own directory. **Do This:** * Create a "src" directory as the root of your application code. * Inside "src", create directories for each feature (e.g., "src/users", "src/products"). * Within each feature directory, organize components, hooks, and styles related to that feature. * Create a "src/shared" directory for components, hooks, and utilities that are used across multiple features. * Use "src/app" to implement the app layout, and routing infrastructure using Next.js using the "app/" directory. **Don't Do This:** * Organize code by technology (e.g., "src/components", "src/hooks", "src/styles"). This makes it harder to find related code. * Place all components in a single directory. **Why:** * **Improved Discoverability:** Feature-based structure makes it easier to find all code related to a specific feature. * **Reduced Complexity:** Features are encapsulated within their own directories, reducing overall project complexity. * **Better Collaboration:** Teams can work on different features without interfering with each other. * **Easier Removal:** It's easier to remove or refactor a specific feature without impacting other parts of the application. **Example:** """ src/ ├── app/ │ ├── layout.tsx │ └── page.tsx ├── components/ │ ├── UserProfile/ │ │ ├── UserProfile.jsx │ │ ├── UserProfile.module.css │ │ └── index.js │ ├── ProductList/ │ │ ├── ProductList.jsx │ │ ├── ProductList.module.css │ │ └── index.js │ └── shared/ │ ├── Button/ │ │ ├── Button.jsx │ │ └── Button.module.css │ └── Input/ │ ├── Input.jsx │ └── Input.module.css ├── hooks/ │ ├── useAuth.js │ └── useFetch.js ├── services/ │ └── api.js └── utils/ └── helpers.js """ ### 2.2. Naming Conventions Consistent naming conventions improve code readability and maintainability. **Do This:** * Use PascalCase for component names (e.g., "UserProfile", "ProductList"). * Use camelCase for variable names (e.g., "userName", "productList"). * Use descriptive and meaningful names. * Prefix custom hooks with "use" (e.g., "useWindowSize", "useFetchData"). * Name CSS modules with ".module.css" or ".module.scss" extensions (e.g., "UserProfile.module.css"). * Use SCREAMING_SNAKE_CASE for constants. **Don't Do This:** * Use cryptic or abbreviated names. * Use names that conflict with existing libraries or frameworks. * Use inconsistent naming styles. **Why:** * **Readability:** Consistent naming makes code easier to understand. * **Maintainability:** Naming conventions help developers quickly identify the purpose of code elements. * **Collaboration:** Consistent naming reduces ambiguity and facilitates collaboration within the team. **Example:** """javascript // Component Name: PascalCase const UserProfile = () => { ... }; // Variable Name: camelCase const userName = 'John Doe'; // Custom Hook Name: use prefix const useWindowSize = () => { ... }; // CSS Module Name: .module.css extension import styles from './UserProfile.module.css'; // Constant Name: SCREAMING_SNAKE_CASE const API_ENDPOINT = 'https://api.example.com'; """ ### 2.3. Module Resolution Use absolute imports and aliases to improve code readability and avoid relative path hell. **Do This:** * Configure your build tool (e.g., Webpack, Parcel) to support absolute imports. * Define aliases for commonly used directories (e.g., "@components", "@hooks", "@utils"). * Use absolute imports with aliases in your code. **Don't Do This:** * Use deeply nested relative paths (e.g., "../../../../components/Button"). **Why:** * **Readability:** Absolute imports are easier to read and understand. * **Maintainability:** Absolute imports are less prone to breaking when files are moved. * **Refactoring:** Easier to refactor the project without breaking imports. **Example:** """javascript // Webpack Configuration (example) module.exports = { resolve: { alias: { '@components': path.resolve(__dirname, 'src/components/'), '@hooks': path.resolve(__dirname, 'src/hooks/'), '@utils': path.resolve(__dirname, 'src/utils/'), }, }, }; // Usage in a Component import Button from '@components/Button'; import useWindowSize from '@hooks/useWindowSize'; import { formatDate } from '@utils/helpers'; """ ## 3. Data Management Proper data management is crucial for performance and maintainability. This section outlines the preferred approaches for managing data in React applications. ### 3.1. State Management Libraries Choose the right state management library based on the complexity of your application. For very simple cases, the default React Context API and "useState" and "useReducer" hooks may suffice. For medium-complexity apps, consider "Zustand" due to its simplicity. For applications that require more complex global state management, use Redux Toolkit. **Do This:** * Start with React's built-in state management tools ("useState", "useContext", "useReducer"). * Consider Zustand for medium-complexity apps for its simplicity and ease of use. It uses a very simple mental model: a single global store with selectors. You can easily create multiple stores this way, if necessary. * Use Redux Toolkit for applications with complex state management requirements. * Use middleware (e.g., Redux Thunk, Redux Saga) for handling asynchronous actions in Redux. * Use selectors to derive data from the state. **Don't Do This:** * Use Redux for simple applications where component-level state is sufficient. * Mutate state directly in Redux. * Over-engineer state management. **Why:** * **Centralized State:** State management libraries provide a centralized store for managing application state. * **Predictable State Updates:** Redux enforces a unidirectional data flow, making state updates predictable. * **Improved Performance:** Selectors and memoization techniques can improve performance by preventing unnecessary re-renders. **Example (Redux Toolkit):** """javascript // store.js import { configureStore } from '@reduxjs/toolkit'; import counterReducer from '../features/counter/counterSlice'; export const store = configureStore({ reducer: { counter: counterReducer, }, }); // counterSlice.js import { createSlice } from '@reduxjs/toolkit'; const counterSlice = createSlice({ name: 'counter', initialState: { value: 0, }, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, }, }); export const { increment, decrement } = counterSlice.actions; export default counterSlice.reducer; // Component import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { increment, decrement } from './counterSlice'; const Counter = () => { const count = useSelector((state) => state.counter.value); const dispatch = useDispatch(); return ( <div> <button onClick={() => dispatch(increment())}>Increment</button> <span>{count}</span> <button onClick={() => dispatch(decrement())}>Decrement</button> </div> ); }; export default Counter; """ **Example (Zustand):** """javascript import { create } from 'zustand' import { devtools, persist } from 'zustand/middleware' let store = (set) => ({ darkMode: false, toggleDarkMode: () => set((state) => ({ darkMode: !state.darkMode })), }) store = devtools(store) store = persist(store, { name: 'darkMode' }) const useStore = create(store) export default useStore; //Example of usage of zustand store import useStore from './store'; const DarkModeButton = () => { const darkMode = useStore((state) => state.darkMode); const toggleDarkMode = useStore((state) => state.toggleDarkMode); return ( <button onClick={toggleDarkMode}> {darkMode ? 'Light Mode' : 'Dark Mode'} </button> ); }; export default DarkModeButton; """ ### 3.2. Data Fetching Use appropriate data fetching techniques based on the type of data and the frequency of updates. **Do This:** * Use the browser's "fetch" API with "async/await" syntax for making HTTP requests. * Consider third-party libraries like Axios or "ky" for additional features (e.g., interceptors, automatic retries). * Use React Query or SWR for caching, deduplication, and background updates. * Implement error handling and loading states. * Consider React Server Components and the "use" hook for server-side data fetching. **Don't Do This:** * Fetch data directly in component render methods. * Ignore error handling. * Over-fetch data. **Why:** * **Improved Performance:** Caching and deduplication reduce the number of network requests. * **Better User Experience:** Loading states and error handling provide feedback to the user. * **Simplified Code:** Libraries like React Query and SWR simplify data fetching logic. **Example (React Query):** """jsx import { useQuery } from '@tanstack/react-query'; const fetchTodos = async () => { const response = await fetch('https://jsonplaceholder.typicode.com/todos'); if (!response.ok) { throw new Error('Failed to fetch todos'); } return response.json(); }; const Todos = () => { const { data, isLoading, isError, error } = useQuery('todos', fetchTodos); if (isLoading) { return <p>Loading todos...</p>; } if (isError) { return <p>Error: {error.message}</p>; } return ( <ul> {data.map((todo) => ( <li key={todo.id}>{todo.title}</li> ))} </ul> ); }; export default Todos; """ ## 4. Styling Consistent and maintainable styling is essential for a good user experience. ### 4.1. CSS-in-JS Use CSS-in-JS libraries like styled-components or Emotion for modular and maintainable styling. **Do This:** * Use styled-components or Emotion to define component-specific styles. * Use theming to manage global styles and branding. * Use "css" prop with Emotion for dynamic styles/one-off customizations. **Don't Do This:** * Write inline styles directly in JSX (except for very simple cases). * Use global CSS classes that can conflict with other components. **Why:** * **Component-Scoped Styles:** CSS-in-JS styles are scoped to individual components, preventing naming collisions. * **Dynamic Styles:** CSS-in-JS allows you to easily apply dynamic styles based on props or state. * **Improved DX:** Streamlined styling workflow with JavaScript integration. **Example (styled-components):** """jsx import styled from 'styled-components'; const Button = styled.button" background-color: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; &:hover { background-color: #0056b3; } "; const PrimaryButton = styled(Button)" font-weight: bold; " const MyComponent = () => { return <Button>Click Me</Button>; }; export default MyComponent; """ ## 5. Testing Automated testing is crucial for ensuring the quality of your code. Use frameworks like Jest and React Testing Library for unit and integration tests. ### 5.1. Testing Strategy Write unit tests, integration tests, and end-to-end tests to cover different aspects of your application. **Do This:** * Use Jest as the test runner. * Use React Testing Library for rendering components and simulating user interactions. * Write unit tests for individual components, hooks, and utilities. * Write integration tests to verify interactions between components. * Use tools like Cypress or Playwright for end-to-end tests. * Aim for high test coverage. * Encourage TDD (Test Driven Development). **Don't Do This:** * Skip writing tests. * Write tests that are too tightly coupled to implementation details. * Rely solely on manual testing. **Why:** * **Improved Code Quality:** Testing helps catch bugs early in the development process. * **Increased Confidence:** Testing provides confidence when making changes or refactoring code. * **Reduced Risk:** Testing reduces the risk of introducing regressions. **Example (React Testing Library):** """jsx import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import Button from './Button'; test('renders button with correct text', () => { render(<Button>Click Me</Button>); const buttonElement = screen.getByText(/Click Me/i); expect(buttonElement).toBeInTheDocument(); }); test('calls onClick handler when button is clicked', () => { const onClick = jest.fn(); render(<Button onClick={onClick}>Click Me</Button>); const buttonElement = screen.getByText(/Click Me/i); fireEvent.click(buttonElement); expect(onClick).toHaveBeenCalledTimes(1); }); """
# State Management Standards for React This document outlines standards and best practices for state management in React applications. It aims to guide developers in choosing appropriate state management solutions, implementing them effectively, and writing maintainable, performant, and scalable React code. ## 1. Choosing the Right State Management Approach Selecting the right state management is crucial for structuring React applications effectively. The choice depends on application size, complexity, and team preferences. ### 1.1. Standard: Understanding State Management Options * **Considerations:** * **Component State (useState, useReducer):** Suitable for simple, localized state within individual components. * **Context API (useContext):** Appropriate for prop drilling avoidance and sharing data between components at different levels in the component tree, for data that changes infrequently. * **Third-Party Libraries (Redux, Zustand, Recoil, Jotai):** Necessary for complex applications requiring global state management, predictable state mutations, and advanced features like middleware or atom-based state. * **Do This:** Carefully evaluate your application's requirements before choosing a state management approach. Start with simpler solutions like component state and Context API and only adopt more complex libraries if truly needed. * **Don't Do This:** Over-engineer state management by using a global state management solution for a small application where component state or Context API would suffice. ### 1.2. Standard: Application Size and Complexity as Determinants * **Considerations:** * **Small Applications:** Favor "useState" and "useReducer" for component-level state. Context API is useful for theme or user settings * **Medium-Sized Applications:** Employ Context API combined with "useReducer" for managing more complex state that needs to be shared across a moderate number of components. * **Large Applications:** Utilize state management libraries like Redux, Zustand, Recoil, or Jotai for centralized control, predictability, and scalability. * **Do This:** Document your rationale for choosing a particular state management solution, outlining its benefits and trade-offs for your specific application context. * **Don't Do This:** Migrating between state management solutions mid-development. This is costly and risky. Make an informed decision upfront. ### 1.3. Standard: Team Familiarity and Ecosystem * **Considerations:** * **Team Expertise:** Select solutions that your team is already familiar with or willing to learn quickly. * **Ecosystem Support:** Choose libraries with strong community support, comprehensive documentation, and available tooling (e.g., Redux DevTools). * **Do This:** Conduct workshops or training sessions to familiarize the team with new state management libraries before adopting them in a project. * **Don't Do This:** Pick a niche or experimental state management library without considering its maturity, community support, and long-term maintainability. ## 2. Component State Management (useState, useReducer) "useState" and "useReducer" are essential for managing local component state in React. Understanding their proper use is crucial for building efficient components. ### 2.1. Standard: useState for Simple State * **Considerations:** "useState" is ideal for managing simple data types like strings, numbers, booleans, and arrays that don't require complex update logic. * **Do This:** Use descriptive variable names for state and update functions. * **Don't Do This:** Store derived data in "useState". Instead, calculate it within the component's render logic using "useMemo". Re-calculating values is acceptable as long as it does not cause performance bottlenecks. """jsx import React, { useState, useMemo } from 'react'; function Counter() { const [count, setCount] = useState(0); const [multiplier, setMultiplier] = useState(2); const doubledCount = useMemo(() => { console.log('Calculating doubled count'); // Only calculate when count or multiplier changes return count * multiplier; }, [count, multiplier]); return ( <div> <p>Count: {count}</p> <p>Doubled Count: {doubledCount}</p> <button onClick={() => setCount(count + 1)}>Increment</button> <button onClick={() => setMultiplier(multiplier + 1)}>Increment Multiplier</button> </div> ); } """ * **Why:** Clear naming enhances readability and maintainability. Avoiding redundant state prevents inconsistencies and improves performance. "useMemo" prevents expensive calculations on every re-render if the dependent values have not changed. ### 2.2. Standard: useReducer for Complex State * **Considerations:** "useReducer" is suitable for managing state with multiple sub-values, complex or related update logic, or when state updates depend on the previous state. * **Do This:** Define a clear reducer function that handles different actions consistently and returns the new state immutably. * **Don't Do This:** Directly mutate the state within the reducer. Always return a new state object or array. """jsx import React, { useReducer } from 'react'; const initialState = { count: 0, step: 1 }; function reducer(state, action) { switch (action.type) { case 'increment': return { ...state, count: state.count + state.step }; case 'decrement': return { ...state, count: state.count - state.step }; case 'setStep': return { ...state, step: action.payload }; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <div> <p>Count: {state.count}</p> <p>Step: {state.step}</p> <button onClick={() => dispatch({ type: 'increment' })}>Increment</button> <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button> <button onClick={() => dispatch({ type: 'setStep', payload: 5 })}>Set Step to 5</button> </div> ); } """ * **Why:** Reducers promote predictable state updates and simplify debugging. Immutability ensures that React can efficiently detect changes and re-render components. ### 2.3. Standard: Grouping Related State * **Considerations:** When dealing with multiple related pieces of state, group them into a single state object managed by "useState" or "useReducer". * **Do This:** Create cohesive state objects to represent logical units of data. * **Don't Do This:** Scatter related state across multiple independent "useState" hooks, as it can make components harder to reason about. """jsx import React, { useState } from 'react'; function Form() { const [formData, setFormData] = useState({ firstName: '', lastName: '', email: '', }); const handleChange = (e) => { const { name, value } = e.target; setFormData({ ...formData, [name]: value }); }; return ( <form> <input type="text" name="firstName" value={formData.firstName} onChange={handleChange} /> <input type="text" name="lastName" value={formData.lastName} onChange={handleChange} /> <input type="email" name="email" value={formData.email} onChange={handleChange} /> </form> ); } """ * **Why:** Grouping related state improves code organization and simplifies state updates. ## 3. Context API for Prop Drilling Avoidance The Context API provides a way to pass data through the component tree without manually passing props at every level. ### 3.1. Standard: Context Usage for Shared Data * **Considerations:** Context is excellent for theme settings, authentication status, or user preferences. * **Do This:** Create a context provider at the top level where the data is needed and consume the context using "useContext" in descendant components. """jsx import React, { createContext, useContext, useState } from 'react'; const ThemeContext = createContext(); function ThemeProvider({ children }) { const [theme, setTheme] = useState('light'); const toggleTheme = () => { setTheme(theme === 'light' ? 'dark' : 'light'); }; return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); } function useTheme() { return useContext(ThemeContext); } function ThemedComponent() { const { theme, toggleTheme } = useTheme(); return ( <div style={{ backgroundColor: theme === 'light' ? 'white' : 'black', color: theme === 'light' ? 'black' : 'white' }}> <p>Current Theme: {theme}</p> <button onClick={toggleTheme}>Toggle Theme</button> </div> ); } function App() { return ( <ThemeProvider> <ThemedComponent /> </ThemeProvider> ); } """ * **Why:** Context reduces prop drilling, making components more modular and reusable. ### 3.2. Standard: Context with useReducer for Complex State * **Considerations:** For more complex context data with predictable updates, use the Context API in conjunction with "useReducer". * **Do This:** Encapsulate the state logic within a reducer and provide the dispatch function through the context provider. * **Don't Do This:** Overuse context for highly dynamic data that changes frequently, as it can lead to performance issues due to unnecessary re-renders. Consider libraries designed for performance optimization in such cases. """jsx import React, { createContext, useContext, useReducer } from 'react'; const CartContext = createContext(); const initialState = { items: [] }; function cartReducer(state, action) { switch (action.type) { case 'ADD_ITEM': return { ...state, items: [...state.items, action.payload] }; case 'REMOVE_ITEM': return { ...state, items: state.items.filter(item => item.id !== action.payload) }; default: return state; } } function CartProvider({ children }) { const [state, dispatch] = useReducer(cartReducer, initialState); return ( <CartContext.Provider value={{ state, dispatch }}> {children} </CartContext.Provider> ); } function useCart() { return useContext(CartContext); } function CartItem({ item }) { const { dispatch } = useCart(); return ( <li> {item.name} - ${item.price} <button onClick={() => dispatch({ type: 'REMOVE_ITEM', payload: item.id })}> Remove </button> </li> ); } function ShoppingCart() { const { state } = useCart(); return (<ul>{state.items.map(item => <CartItem key={item.id} item={item} />)}</ul>) } function App() { return ( <CartProvider> <ShoppingCart /> </CartProvider> ); } """ * **Why:** Combining Context API with "useReducer" allows for effective management of global/shared state with more complex update mechanisms. ## 4. Third-Party State Management Libraries When component state or Context API becomes insufficient, consider using dedicated state management libraries. Here are some popular options: ### 4.1. Standard: Redux for Predictable State * **Considerations:** Redux promotes a unidirectional data flow with centralized state, actions, and reducers. * **Do This:** Follow the Redux principles strictly by defining clear actions, reducers, and middleware (e.g., Redux Thunk for asynchronous actions). Use Redux Toolkit to simplify Redux setup and reduce boilerplate. """jsx // Store.js using Redux Toolkit import { configureStore } from '@reduxjs/toolkit'; import counterReducer from './features/counter/counterSlice'; export const store = configureStore({ reducer: { counter: counterReducer, }, }); // features/counter/counterSlice.js import { createSlice } from '@reduxjs/toolkit'; const initialState = { value: 0, }; export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; }, }, }); export const { increment, decrement, incrementByAmount } = counterSlice.actions; export default counterSlice.reducer; // Component import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { increment, decrement, incrementByAmount } from './features/counter/counterSlice'; function Counter() { const count = useSelector((state) => state.counter.value); const dispatch = useDispatch(); return ( <div> <p>Count: {count}</p> <button onClick={() => dispatch(increment())}>Increment</button> <button onClick={() => dispatch(decrement())}>Decrement</button> <button onClick={() => dispatch(incrementByAmount(5))}>Increment by 5</button> </div> ); } """ * **Why:** Redux ensures predictable state management, making it easier to debug and test complex applications. Redux Toolkit reduces setup complexity and provides best-practice configurations. ### 4.2. Standard: Zustand for Simplicity and Ease of Use * **Considerations:** Zustand offers a simpler and more lightweight alternative to Redux with a focus on ease of use and minimal boilerplate. * **Do This:** Define stores using the "create" function and access state and actions directly within components using "useStore". """jsx import create from 'zustand'; const useStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), })); function Counter() { const { count, increment, decrement } = useStore(); return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> ); } """ * **Why:** Zustand simplifies state management, making it a good choice for smaller to medium-sized applications where Redux might be overkill. ### 4.3. Standard: Recoil for Fine-Grained Updates * **Considerations:** Recoil introduces the concept of atoms (units of state) and selectors (derived state), allowing for fine-grained updates and efficient re-renders. * **Do This:** Define atoms for individual pieces of state and selectors for computed values that depend on those atoms. """jsx import { RecoilRoot, atom, useRecoilState } from 'recoil'; const countState = atom({ key: 'countState', default: 0, }); function Counter() { const [count, setCount] = useRecoilState(countState); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> <button onClick={() => setCount(count - 1)}>Decrement</button> </div> ); } function App() { return ( <RecoilRoot> <Counter /> </RecoilRoot> ); } """ * **Why:** Recoil optimizes performance by only re-rendering components that depend on the specific atoms that have changed. ### 4.4. Immer.js for Simplified Immutable Updates * **Considerations**: Immer simplifies immutable updates by allowing you to work with a mutable draft of the state. Immer ensures that you never accidentally modify the original state object. """jsx import { useImmer } from "use-immer"; function Form() { const [person, updatePerson] = useImmer({ firstName: "Bob", lastName: "Smith", }); function handleFirstNameChange(e) { updatePerson(draft => { draft.firstName = e.target.value; }); } function handleLastNameChange(e) { updatePerson(draft => { draft.lastName = e.target.value; }); } return ( <> <input value={person.firstName} onChange={handleFirstNameChange} /> <input value={person.lastName} onChange={handleLastNameChange} /> <p> Hello, {person.firstName} {person.lastName}! </p> </> ); } """ * **Why**: Reduces boilerplate in reducers and simplifies complex state mutations. ## 5. Best Practices and Anti-Patterns ### 5.1. Standard: Immutability * **Do This:** Always treat state as immutable. Create new state objects or arrays instead of modifying existing ones. * **Don't Do This:** Directly mutate the state. """jsx // Correct: Creating a new array const addItem = (item) => { setItems([...items, item]); }; // Incorrect: Mutating the existing array const addItem = (item) => { items.push(item); // BAD! Directly mutating state setItems(items); // React may not detect the change! }; """ * **Why:** Immutability ensures predictable state management, simplifies debugging, and allows React to optimize rendering performance. ### 5.2. Standard: Avoiding Unnecessary Re-renders * **Do This:** Use "React.memo", "useMemo", and "useCallback" to prevent components from re-rendering when their props or dependencies haven't changed. * **Don't Do This:** Pass new objects or function instances as props to child components on every render, as this will always trigger re-renders. """jsx import React, { useState, useCallback, memo } from 'react'; const MyComponent = memo(({ onClick, data }) => { console.log('MyComponent rendered'); return ( <button onClick={onClick}>{data.value}</button> ); }); function ParentComponent() { const [count, setCount] = useState(0); const data = { value: count }; // This will cause re-renders //Create the function one time with useCallback const handleClick = useCallback(() => { setCount(count + 1); }, [count]); return ( <div> <MyComponent onClick={handleClick} data={data} /> </div> ); } """ * **Why:** Optimizing re-renders improves the performance of React applications, especially those with complex UIs. ### 5.3. Standard: Async State Updates * **Do This:** When updating state based on previous state asynchronously (e.g., after an API call), use the functional form of "setState" or dispatch actions with computed payloads based on the existing state. * **Don't Do This:** Rely on the value of state variables immediately after calling "setState", as the update is asynchronous and the value might not be updated yet. """jsx import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); const incrementAsync = () => { setTimeout(() => { setCount((prevCount) => prevCount + 1); // Functional update }, 1000); }; return ( <div> <p>Count: {count}</p> <button onClick={incrementAsync}>Increment Async</button> </div> ); } """ * **Why:** Using the functional form ensures that you're updating the state based on the correct previous value, even in asynchronous scenarios. This document provides a comprehensive guideline for managing state in React applications. Following these standards will result in more maintainable, performant, and scalable React applications. Regularly review and update these standards to align with the evolving landscape of React development.
# Performance Optimization Standards for React This document outlines performance optimization standards for React applications. It aims to guide developers in building efficient, responsive, and resource-friendly user interfaces. These standards are designed to be used in conjunction with AI coding assistants to ensure consistency and excellence in React code. ## 1. General Principles ### 1.1. Minimize Re-renders **Do This:** * Understand the React render lifecycle and optimize component updates. **Don't Do This:** * Rely on default rendering behavior without considering performance implications. **Why:** Frequent, unnecessary re-renders are a major source of performance bottlenecks in React applications. Optimizing re-renders directly impacts responsiveness and resource consumption. **Example:** """jsx import React, { useState, useCallback } from 'react'; const MyComponent = React.memo(({ data, onUpdate }) => { console.log("MyComponent re-rendered"); // Track re-renders return ( <div> <p>Value: {data.value}</p> <button onClick={onUpdate}>Update</button> </div> ); }); function ParentComponent() { const [value, setValue] = useState(0); const data = { value }; const onUpdate = useCallback(() => { setValue(prevValue => prevValue + 1); }, []); return ( <MyComponent data={data} onUpdate={onUpdate} /> ); } export default ParentComponent; """ **Explanation:** * "React.memo" prevents re-renders if the props haven't changed (shallow comparison by default). * "useCallback" memoizes the "onUpdate" function, so a new function isn't created on every render of "ParentComponent". This is crucial because if a new function is passed as a prop to "MyComponent", "React.memo" will not prevent a re-render since the prop has changed. * The console log helps debug and identify unnecessary re-renders. ### 1.2. Use Immutable Data Structures **Do This:** * Treat data as immutable and create new objects/arrays when modifying. **Don't Do This:** * Directly mutate objects or arrays passed as props or state. **Why:** Immutable data structures simplify change detection and enable efficient re-render optimizations. React's "memo", "PureComponent", and context API rely on prop/state comparisons, which are much faster with immutability. **Example:** """jsx import React, { useState } from 'react'; import { useImmer } from 'use-immer'; function ObjectComponent() { const [person, updatePerson] = useImmer({ name: 'John Doe', age: 30, }); const handleAgeIncrease = () => { updatePerson(draft => { draft.age++; }); }; return ( <div> <p>Name: {person.name}</p> <p>Age: {person.age}</p> <button onClick={handleAgeIncrease}>Increase Age</button> </div> ); } export default ObjectComponent; """ **Explanation:** * "useImmer" simplifies the process of working with immutable data structures. It allows you to work with a mutable "draft" of your state within the updater function, and Immer takes care of producing the immutable updates efficiently. * Instead of directly mutating "person.age", we use the "updatePerson" function to create a new object with the updated age. * This ensures that React can detect changes and trigger re-renders only when necessary. ### 1.3. Virtualize Long Lists **Do This:** * Use virtualization libraries like "react-window" or "react-virtualized" for rendering large lists. **Don't Do This:** * Render all list items at once, regardless of visibility. **Why:** Rendering thousands of list items can severely impact performance. Virtualization renders only the visible items (and a small buffer), significantly reducing initial load time and improving scrolling performance. **Example:** """jsx import React from 'react'; import { FixedSizeList as List } from 'react-window'; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); function VirtualizedList({ itemCount }) { return ( <List height={150} // total height of the list container width={300} // Width of the list container itemSize={35} // Height of a row itemCount={itemCount} //number of rows > {Row} </List> ); } export default VirtualizedList; """ **Explanation:** * "react-window" renders only the visible rows. * It requires specifying the "height", "width", "itemSize", and "itemCount". * The "Row" component receives the "style" prop that positions the row correctly. ## 2. Component-Level Optimization ### 2.1. Code Splitting **Do This:** * Use dynamic imports to split your application into smaller chunks. **Don't Do This:** * Load all code upfront, even if it's not immediately needed. **Why:** Code splitting reduces the initial load time by downloading only the necessary code for the current view. This improves the perceived performance, especially on slow network connections. **Example:** """jsx import React, { Suspense } from 'react'; // Function Component function MyComponent() { const LazyComponent = React.lazy(() => import('./LazyComponent')); return ( <Suspense fallback={<div>Loading...</div>}> <LazyComponent /> </Suspense> ); } export default MyComponent; // LazyComponent.jsx function LazyComponent() { return ( <div> This component is loaded lazily! </div> ); } """ **Explanation:** * "React.lazy" dynamically imports the "LazyComponent". * "Suspense" provides a fallback UI while the component is loading. This prevents a blank screen and provides a smoother user experience. * The "LazyComponent" is loaded only when it is rendered, reducing the initial bundle size. ### 2.2. Debouncing and Throttling **Do This:** * Use debouncing or throttling to limit the frequency of expensive operations like API calls or state updates in response to frequent events (e.g., typing in an input, scrolling). **Don't Do This:** * Perform expensive operations on every event, leading to performance degradation. **Why:** Debouncing and throttling prevent resource-intensive operations from being executed too frequently. This improves responsiveness and avoid unnecessary calculations. **Example (Debouncing):** """jsx import React, { useState, useCallback, useRef } from 'react'; function DebouncedInput() { const [inputValue, setInputValue] = useState(''); const debouncedSetInputValue = useDebounce(setInputValue, 500); const inputRef = useRef(null); const handleChange = (e) => { debouncedSetInputValue(e.target.value); }; return ( <div> <input type="text" ref={inputRef} onChange={handleChange} placeholder="Type something..." /> <p>Value: {inputValue}</p> </div> ); } function useDebounce(func, delay) { const timeoutRef = useRef(null); const debouncedFunc = useCallback( (...args) => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } timeoutRef.current = setTimeout(() => { func(...args); }, delay); }, [func, delay] ); return debouncedFunc; } export default DebouncedInput; """ **Explanation:** * "useDebounce" hook delays the execution of the "setInputValue" function by 500ms. * The "handleChange" function calls the debounced version of "setInputValue". * The state "inputValue" is only updated after the user stops typing for 500ms. ### 2.3. Use "useMemo" and "useCallback" Effectively **Do This:** * Memoize expensive calculations and function definitions using "useMemo" and "useCallback". **Don't Do This:** * Overuse "useMemo" and "useCallback" without considering the overhead. Memoization itself has a cost. Only use these hooks for values or functions that are truly expensive to compute or that are passed as props to memoized components. **Why:** "useMemo" and "useCallback" prevent unnecessary recalculations and function re-creation, which can trigger unneeded re-renders. **Example:** """jsx import React, { useState, useMemo, useCallback } from 'react'; function ExpensiveComponent({ multiplier }) { const [count, setCount] = useState(1); const expensiveCalculation = useMemo(() => { console.log("Calculating..."); let result = 0; for (let i = 0; i < 100000000; i++) { result += i * multiplier; } return result; }, [multiplier]); // Only recalculate when multiplier changes const increment = useCallback(() => { setCount(c => c + 1); }, []); return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> <p>Expensive Result: {expensiveCalculation}</p> </div> ); } export default ExpensiveComponent; """ **Explanation:** * The "expensiveCalculation" is memoized using "useMemo", so it's only recalculated when "multiplier" changes. * The "increment" function is memoized using "useCallback", preventing it from being re-created on every render. ## 3. Data Fetching Optimization ### 3.1. Caching Data **Do This:** * Implement caching strategies using libraries like "react-query" or "swr" to avoid redundant API calls. **Don't Do This:** * Fetch the same data repeatedly without caching. **Why:** Caching significantly reduces network requests and improves performance by serving previously fetched data from memory. **Example (using "react-query"):** """jsx import React from 'react'; import { useQuery } from 'react-query'; async function fetchPosts() { const response = await fetch('/api/posts'); if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); } function Posts() { const { isLoading, error, data } = useQuery('posts', fetchPosts); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {data.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> ); } export default Posts; """ **Explanation:** * "useQuery" automatically caches the data fetched by "fetchPosts". * Subsequent requests for the same data are served from the cache, unless the cache is stale. * "react-query" handles background updates, retries, and error handling automatically. ### 3.2. Pagination and Lazy Loading **Do This:** * Implement pagination for large datasets and lazy load data as the user scrolls or interacts with the UI. **Don't Do This:** * Load the entire dataset at once, overwhelming the client. **Why:** Pagination and lazy loading reduce the initial data load and improve perceived performance. **Example (simple pagination):** """jsx import React, { useState, useEffect } from 'react'; function PaginatedList() { const [items, setItems] = useState([]); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const itemsPerPage = 10; useEffect(() => { const fetchData = async () => { setLoading(true); const response = await fetch("/api/items?page=${page}&limit=${itemsPerPage}"); const data = await response.json(); setItems(data); setLoading(false); }; fetchData(); }, [page, itemsPerPage]); const handleNextPage = () => { setPage(prevPage => prevPage + 1); }; return ( <div> {loading ? ( <div>Loading...</div> ) : ( <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> )} <button onClick={handleNextPage} disabled={loading}> Next Page </button> </div> ); } export default PaginatedList; """ **Explanation:** * The "useEffect" hook fetches data for the current page. * The "handleNextPage" function updates the page number. * The API endpoint "/api/items" should handle the pagination parameters ("page" and "limit"). ## 4. Image Optimization ### 4.1. Optimize Image Sizes **Do This:** * Resize images to the appropriate dimensions for their intended display size. **Don't Do This:** * Serve large, high-resolution images for small thumbnails. **Why:** Large images consume unnecessary bandwidth and increase load times. ### 4.2. Use Modern Image Formats **Do This:** * Use modern image formats like WebP or AVIF for better compression and quality. **Don't Do This:** * Rely solely on older formats like JPEG or PNG. **Why:** WebP and AVIF offer superior compression compared to JPEG and PNG, resulting in smaller file sizes without significant quality loss. **Example:** """jsx import React from 'react'; import myImage from './assets/my-image.webp'; // Import WebP image function ImageComponent() { return ( <img src={myImage} alt="My Optimized Image" /> ); } export default ImageComponent; """ ### 4.3. Lazy Load Images **Do This:** * Use the "loading="lazy"" attribute or a library like "react-lazyload" to lazy load images. **Don't Do This:** * Load all images upfront, even those below the fold. **Why:** Lazy loading images defers the loading of off-screen images until they are about to become visible, improving initial page load time. **Example:** """jsx import React from 'react'; function LazyImage({ src, alt }) { return ( <img src={src} alt={alt} loading="lazy" /> ); } export default LazyImage; """ ## 5. Performance Profiling and Monitoring ### 5.1. Use React Profiler **Do This:** * Use the React Profiler (available in React DevTools) to identify performance bottlenecks. **Don't Do This:** * Guess at performance issues without profiling. **Why:** The React Profiler provides detailed information about component render times, allowing you to pinpoint performance bottlenecks. **How to Use:** 1. Open React DevTools in your browser. 2. Navigate to the Profiler tab. 3. Record a performance profile while interacting with your application. 4. Analyze the flame graph and component timings to identify slow components. ### 5.2. Monitor Performance in Production **Do This:** * Use performance monitoring tools to track key metrics like load time, time to interactive, and frame rate in production. **Don't Do This:** * Assume that performance issues only exist in development. **Why:** Production performance can differ significantly from development performance due to factors like network conditions, device capabilities, and user behavior. Monitoring allows you to identify and address real-world performance issues. Tools like Google Analytics, New Relic, and Sentry can be used to monitor performance. ## 6. Avoiding Anti-Patterns ### 6.1. Deep Component Trees and Context API **Anti-Pattern:** * Overusing the Context API for passing props through deeply nested component trees without memoization. **Why:** Every time the context value changes, all components consuming that context will re-render, even if they don't directly use the changed value. This can lead to significant performance issues in large applications. **Solution:** * Use more specific context providers closer to the consuming components. * Memoize context consumers using "React.memo". * Consider prop drilling for components that are very far apart if the component in the middle does not need the props. ### 6.2. Unnecessary State **Anti-Pattern:** * Storing derived data in state. **Why:** Storing data that can be easily derived from other state values leads to unnecessary re-renders and complexity. **Solution:** * Calculate derived data directly in the render function or using "useMemo". * Keep state minimal and focused on the core data that drives the application. ### 6.3. Inline Styles and Functions **Anti-Pattern:** * Defining styles and functions directly within the component's render method. **Why:** Inline styles and functions are re-created on every render, leading to unnecessary re-renders of child components that rely on reference equality. **Solution:** * Define styles as separate CSS classes or using a styling library like Styled Components or Emotion. * Memoize functions using "useCallback". ## 7. Technology-Specific Details ### 7.1. React Server Components (RSC) **Consider:** * Using React Server Components (when available) as they allow you to execute code on the server, minimizing the amount of JavaScript sent to the client. * Shift logic that doesn't require client-side interactivity to Server Components. RSC is still an evolving technology so its practical application might be limited depending on specific project needs. Also note the dependency on a framework implementing RSC (e.g., Next.js, Remix) ### 7.2. Transitions API (<Suspense Transitions>) **Consider:** * Leveraging the Transitions API to mark non-urgent state updates. This helps React prioritize updates, ensuring that urgent updates (like typing) remain responsive, while less important updates (like data fetching) are deferred. ### 7.3. Bundler Configuration **Consider:** * Using a modern bundler like Webpack, Parcel, or Rollup with appropriate configuration for code splitting and tree shaking. * Ensuring proper minification and compression of production bundles. * Analyzing bundle sizes to identify large dependencies that can be optimized or replaced. ## Conclusion By adhering to these performance optimization standards, React developers can build efficient, responsive, and maintainable applications. This document serves as a comprehensive guide for writing high-quality React code and leveraging the best tools and techniques for performance optimization. Remember to profile your applications regularly and adapt these standards to your specific needs and constraints. Using these standards alongside AI coding assistants can automate and enforce these best practices, leading to more consistent and performant codebases.