# 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 (
Click Me
);
}
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 (
{children}
);
}
// Then use it like this:
Click Me
// Bad: Inline Classes
{/* Hard to read and not reusable */}
Click Me
"""
## 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 (
{data.map(item => (
<p>{item.name}</p>
))}
);
}
"""
### 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();
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 (
Loading...}>
);
}
"""
### 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 {data.value};
});
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 {expensiveValue};
}
"""
### 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 (
{data[index].name}
);
}
return (
);
}
"""
### 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., "", "", "", "", "") to structure your React components.
**Don't Do This:** Rely solely on "" and "" 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.
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'
# Deployment and DevOps Standards for React This document outlines the coding standards and best practices for deploying and managing React applications within a DevOps framework. These standards aim to ensure maintainability, performance, security, and a smooth deployment pipeline, leveraging the latest features and patterns in React development. ## 1. Build Processes and Tooling ### 1.1 Standardizing Build Tools **Do This:** * Use established build tools like webpack (with Create React App or custom configuration), Parcel, or esbuild for bundling, transpilation, and optimization. * Automate the build process using npm scripts or yarn scripts. * Utilize environment variables to configure the build process for different environments (development, staging, production). **Don't Do This:** * Manually manage dependencies or build processes. * Hardcode environment-specific configurations within the application code. * Rely on outdated or unsupported build tools. **Why:** Standardized build tools ensure consistency across environments, simplify dependency management, and allow for automated optimization. **Example:** """json // package.json - scripts section { "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "build:staging": "cross-env REACT_APP_ENV=staging react-scripts build", "build:production": "cross-env REACT_APP_ENV=production react-scripts build" }, "dependencies": { "cross-env": "^7.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", //... other dependencies } } """ ### 1.2 Code Splitting **Do This:** * Implement code splitting using dynamic "import()" statements (lazy loading) to reduce the initial bundle size. * Utilize React.lazy and Suspense for loading components on demand. * Analyze bundle sizes using tools like "webpack-bundle-analyzer" to identify opportunities for further splitting. **Don't Do This:** * Load the entire application code upfront. * Neglect to analyze bundle sizes and optimize for performance. * Create large, monolithic bundles. **Why:** Code splitting improves initial load times, reduces resource consumption, and enhances the overall user experience. **Example:** """jsx // Lazy loading a component import React, { Suspense } from 'react'; const LazyComponent = React.lazy(() => import('./MyComponent')); function MyPage() { return ( <Suspense fallback={<div>Loading...</div>}> <LazyComponent /> </Suspense> ); } export default MyPage; """ ### 1.3 Image Optimization **Do This:** * Optimize images using tools like "imagemin" or "tinypng" before deployment. * Use responsive images with the "<picture>" element or "srcset" attribute to serve appropriately sized images for different devices. * Consider using modern image formats like WebP for better compression and quality. * Implement lazy loading for images using libraries like "react-lazyload". **Don't Do This:** * Deploy large, unoptimized images. * Serve the same image size to all devices. * Ignore the impact of image sizes on page load times. **Why:** Image optimization reduces bandwidth usage, improves page load times, and enhances the user experience, especially on mobile devices. **Example:** """jsx //lazy loading image using react-lazyload import React from 'react'; import LazyLoad from 'react-lazyload'; const MyImage = () => ( <LazyLoad height={200} offset={100}> <img src="my-image.jpg" alt="My Awesome Image" /> </LazyLoad> ); export default MyImage; """ ## 2. CI/CD Pipelines ### 2.1 Automating Deployments **Do This:** * Set up a CI/CD pipeline using tools like Jenkins, GitLab CI, GitHub Actions, CircleCI, or AWS CodePipeline. * Automate the build, testing, and deployment processes. * Implement automated rollback mechanisms in case of deployment failures. **Don't Do This:** * Manually deploy applications. * Skip automated testing in the CI/CD pipeline. * Lack rollback capabilities. **Why:** CI/CD pipelines ensure consistent and reliable deployments, reduce errors, and accelerate the release cycle. **Example (GitHub Actions):** """yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '18' - run: npm install - run: npm run build env: REACT_APP_API_URL: ${{ secrets.REACT_APP_API_URL }} - name: Deploy to Netlify uses: netlify/actions/deploy@v1 with: publishDir: './build' netlify_auth_token: ${{ secrets.NETLIFY_AUTH_TOKEN }} netlify_site_id: ${{ secrets.NETLIFY_SITE_ID }} """ ### 2.2 Environment Configuration **Do This:** * Use environment variables to manage configuration settings for different environments. * Store sensitive information (API keys, database credentials) in secure environment variables (secrets). * Use tools like "dotenv" (in development) or platform-specific configuration management (e.g., AWS Systems Manager Parameter Store) in production. **Don't Do This:** * Hardcode configuration settings in the application code. * Store sensitive information in version control. * Expose sensitive information in client-side code. **Why:** Environment variables allow for flexible and secure configuration management, making it easy to switch between environments without modifying the code. **Example:** """javascript // Accessing environment variables in React const apiUrl = process.env.REACT_APP_API_URL; function MyComponent() { return ( <div> API URL: {apiUrl} </div> ); } export default MyComponent; """ ### 2.3 Feature Flags **Do This:** * Use feature flags to enable or disable features in production without redeploying the application. * Utilize libraries like "react-feature-flags" or cloud-based feature management services like LaunchDarkly or ConfigCat. * Implement robust feature flag management practices, including clear naming conventions and expiration policies. **Don't Do This:** * Rely solely on code deployments to enable or disable features. * Leave feature flags enabled indefinitely. * Lack a system for managing and tracking feature flags. **Why:** Feature flags enable A/B testing, canary releases, and the ability to quickly respond to issues in production without risking a full rollback. **Example:** """jsx // Using react-feature-flags import { FeatureFlag } from 'react-feature-flags'; function MyComponent() { return ( <div> <FeatureFlag flag="new_feature"> <p>This is the new feature!</p> </FeatureFlag> <p>This is the existing feature.</p> </div> ); } export default MyComponent; """ ## 3. Production Considerations ### 3.1 Performance Monitoring **Do This:** * Implement performance monitoring using tools like Google Analytics, New Relic, Sentry, or DataDog. * Track key metrics such as page load times, API response times, and error rates. * Set up alerts to notify developers of performance regressions or critical errors. * Use React Profiler to identify performance bottlenecks within your components. **Don't Do This:** * Deploy applications without performance monitoring. * Ignore performance alerts or error reports. * Fail to regularly review performance metrics and identify areas for improvement. **Why:** Performance monitoring provides valuable insights into application behavior, allowing developers to identify and address performance issues proactively. ### 3.2 Error Tracking **Do This:** * Integrate error tracking using tools like Sentry or Bugsnag to capture and report JavaScript errors, including stack traces and user context. * Configure error tracking to ignore non-critical errors or expected exceptions. * Set up alerts to notify developers of critical errors in production. **Don't Do This:** * Rely solely on user reports to identify errors. * Ignore error reports or fail to investigate the root cause of errors. * Expose sensitive information in error messages. **Why:** Error tracking helps developers quickly identify and resolve errors in production, improving application stability and user satisfaction. **Example:** """javascript //Initializing Sentry import * as Sentry from "@sentry/react"; import { BrowserTracing } from "@sentry/tracing"; Sentry.init({ dsn: "YOUR_SENTRY_DSN", integrations: [new BrowserTracing()], // Set tracesSampleRate to 1.0 to capture 100% // of transactions for performance monitoring. // We recommend adjusting this value in production tracesSampleRate: 0.1, }); //Catching errors with Sentry (example) try { // Your code that might throw an error } catch (error) { Sentry.captureException(error); } """ ### 3.3 Security Hardening **Do This:** * Implement security best practices, such as input validation, output encoding, and Cross-Site Scripting (XSS) prevention. * Use a Content Security Policy (CSP) to restrict the sources of content that the browser is allowed to load. * Keep dependencies up to date to patch security vulnerabilities. * Scrub sensitive data, such as API keys and credentials, from client-side JavaScript bundles. Consider using server-side rendering or a dedicated backend service for sensitive operations. **Don't Do This:** * Trust user input without validation. * Expose sensitive information in client-side code. * Use outdated dependencies with known security vulnerabilities. **Why:** Security hardening protects applications from attacks, safeguards user data, and ensures compliance with security regulations. **Example:** """html // Content Security Policy (CSP) header Content-Security-Policy: default-src 'self'; script-src 'self' https://example.com; style-src 'self' https://example.com; img-src 'self' data:; """ ### 3.4 Server-Side Rendering (SSR) and Static Site Generation (SSG) **Do This:** * Consider using Server-Side Rendering (SSR) with frameworks like Next.js or Remix.js, or Static Site Generation (SSG) with tools like Gatsby or Next.js, for improved SEO, initial load times, and perceived performance. * Implement proper caching strategies for SSR and SSG to minimize server load. * Fine-tune your caching strategy depending on how frequently your data changes. * For SSR, implement streaming for faster Time To First Byte (TTFB). **Don't Do This:** * Use client-side rendering (CSR) for all applications without considering the benefits of SSR or SSG. * Neglect to optimize SSR and SSG for performance. **Why:** SSR and SSG improve SEO, initial load times, and perceived performance, leading to a better user experience and increased visibility. They also provide better accessibility. **Example(Next.js):** """jsx // pages/index.js (Next.js example) function HomePage({ products }) { return ( <ul> {products.map((product) => ( <li key={product.id}>{product.name}</li> ))} </ul> ); } export async function getServerSideProps() { // Fetch data from an API const res = await fetch('https://api.example.com/products'); const products = await res.json(); return { props: { products, }, }; } export default HomePage; """ ## 4. DevOps Practices Specific to React ### 4.1 Infrastructure as Code (IaC) **Do This:** * Use Infrastructure as Code (IaC) tools like Terraform, AWS CloudFormation, or Azure Resource Manager to provision and manage infrastructure resources. * Define infrastructure configurations in code and store them in version control. * Automate all infrastructure changes through CI/CD pipelines. **Don't Do This:** * Manually provision and configure infrastructure resources. * Store infrastructure configurations outside of version control. **Why:** IaC ensures consistent and repeatable infrastructure deployments, reduces errors, and simplifies infrastructure management. ### 4.2 Containerization **Do This:** * Containerize React applications using Docker. * Use Docker Compose to define multi-container applications. * Orchestrate containers using Kubernetes or Docker Swarm. * Utilize multi-stage builds and minimize image sizes. **Don't Do This:** * Deploy applications directly to virtual machines without containerization. * Create large, bloated container images. **Why:** Containerization provides a consistent and isolated environment for running applications, simplifying deployment and scaling. **Example (Dockerfile):** """dockerfile # Stage 1: Build the React application FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build # Stage 2: Serve the built application with Nginx FROM nginx:alpine COPY --from=builder /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] """ ### 4.3 Monitoring and Logging **Do This:** * Implement centralized logging using tools like Elasticsearch, Logstash, and Kibana (ELK stack) or Splunk. * Use structured logging formats like JSON for easier analysis. * Collect application logs, server logs, and network logs. * Implement health checks and uptime monitoring. **Don't Do This:** * Rely solely on local log files. * Fail to monitor application health and uptime. **Why:** Comprehensive monitoring and logging provide valuable insights into application behavior, making it easier to diagnose issues and optimize performance. ### 4.4 Automated Testing **Do This:** * Implement comprehensive automated testing, including unit tests, integration tests, and end-to-end (E2E) tests. * Utilize modern testing libraries and frameworks like Jest, React Testing Library, Cypress, or Playwright. * Integrate testing into your CI/CD pipeline to ensure code quality and prevent regressions. * Write tests for React components to verify correct rendering, state management, and user interactions. **Don't Do This:** * Skip automated testing or rely solely on manual testing. * Write flaky or unreliable tests that produce inconsistent results. *Neglect to update or maintain tests as your application evolves. * Perform all UI testing without mocking out backend requests and state properly. **Why:** Automated testing ensures code quality, reliability, and maintainability. It also reduces the risk of defects making their way into production. """javascript // Unit test example using React Testing Library and Jest 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(); }); """ ### 4.5 Accessibility Testing **Do This:** * Incorporate accessibility testing into the development and deployment process. * Perform automated accessibility checks using tools like axe-core or pa11y. * Manually test accessibility using screen readers and other assistive technologies. * Follow WCAG (Web Content Accessibility Guidelines) standards to ensure your application is usable by people with disabilities. **Don't Do This:** * Ignore accessibility considerations or treat them as an afterthought. * Rely solely on automated testing for accessibility. * Fail to provide alternative text for images or captions for videos. **Why:** Accessibility testing ensures that your application is inclusive and usable by everyone, regardless of their abilities.
# 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); }); """
# Security Best Practices Standards for React This document outlines security best practices for React development. It serves as a guide for developers and AI coding assistants to ensure the creation of secure and robust React applications. ## 1. General Security Principles for React These principles are fundamental for building secure React applications and should be considered throughout the development lifecycle. * **Principle of Least Privilege:** Grant the minimum necessary permissions to users and components. Avoid over-privileged roles. * **Defense in Depth:** Implement multiple layers of security controls to protect against various attack vectors. A single point of failure should not compromise the entire application. * **Input Validation and Sanitization:** Validate and sanitize all user inputs to prevent injection attacks (e.g., XSS, SQL injection). This is paramount, even if backend validation is in place. * **Output Encoding:** Encode data before rendering it to the DOM to prevent XSS attacks. * **Regular Security Audits:** Conduct regular code reviews and security audits to identify and address potential vulnerabilities. * **Keep Dependencies Up-to-Date:** Regularly update all dependencies (React, libraries, packages) to patch known security vulnerabilities. * **Secure Configuration Management:** Store sensitive information (API keys, secrets) securely using environment variables and avoid hardcoding them in the source code. * **Error Handling and Logging:** Implement robust error handling and logging mechanisms to identify and track potential security issues. Ensure that logs do not expose sensitive information. * **Content Security Policy (CSP):** Implement a CSP to control the resources that the browser is allowed to load, mitigating XSS attacks. ## 2. Preventing Cross-Site Scripting (XSS) XSS vulnerabilities allow attackers to inject malicious scripts into your application, potentially stealing user data or performing unauthorized actions. ### 2.1. Output Encoding React escapes values rendered to the DOM by default, which helps prevent certain types of XSS attacks. However, it's crucial to be aware of situations where React's default escaping might not be sufficient. * **Do This:** Rely on React's built-in escaping mechanisms for displaying user-provided data. * **Don't Do This:** Directly render unescaped user input using properties like "dangerouslySetInnerHTML". **Why:** React's default escaping handles common XSS attack vectors by converting special characters into their HTML entities. "dangerouslySetInnerHTML" bypasses this protection and should only be used with carefully sanitized data. **Code Example:** """jsx // Safe - React automatically escapes the username function UserGreeting({ username }) { return <h1>Hello, {username}!</h1>; } // Unsafe - Vulnerable to XSS if username contains malicious code function DangerousUserGreeting({ username }) { return <div dangerouslySetInnerHTML={{ __html: "<h1>Hello, ${username}!</h1>" }} />; } """ * **Why is the second example unsafe?** If "username" contains "<script>alert('XSS')</script>", the browser will execute the script. ### 2.2. Sanitizing HTML When you need to render HTML content from user input, use a trusted sanitization library to remove potentially malicious code. * **Do This:** Use a library like "DOMPurify" or "sanitize-html" to sanitize HTML before rendering it. * **Don't Do This:** Attempt to write your own sanitization logic, as it's complex and prone to errors. **Why:** Sanitization libraries are specifically designed to identify and remove malicious code from HTML, providing a safer way to render user-provided content. **Code Example:** """jsx import DOMPurify from 'dompurify'; function SanitizedHTML({ html }) { const sanitizedHTML = DOMPurify.sanitize(html); return <div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />; } """ * **Key Consideration:** Regularly update your sanitization library to ensure it's aware of the latest XSS vulnerabilities. Configure your library with strict settings and consider using a Content Security Policy to further mitigate risks. ### 2.3. Avoiding URL-based Injection (XSS) Be cautious when using user-provided data in URLs, especially when constructing hyperlinks or redirecting users. * **Do This:** Validate and sanitize URLs before using them in "href" attributes or redirection logic. Use "URL" constructor for validation. * **Don't Do This:** Directly use user input in "href" without validation. Avoid "javascript:" URLs. **Why:** Attackers can inject malicious JavaScript code into URLs, which can be executed when a user clicks on the link or is redirected. **Code Example:** """jsx function SafeLink({ url }) { let safeUrl; try { const parsedUrl = new URL(url); // Only allow http and https protocols if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") { safeUrl = url; } else { safeUrl = "#"; // Invalid URL, prevent navigation } } catch (error) { safeUrl = "#"; // Invalid URL, prevent navigation } return <a href={safeUrl}>Click here</a>; } //Unsafe example - never do this: //<a href={userInputtedURL}>Click Here</a> """ * **Best Practice**: If a URL needs to perform a JavaScript action, use React's event handling instead of "javascript:" URLs: """jsx function ButtonWithAction({ action }) { const handleClick = () => { // Perform the action here (e.g., dispatch an event, update state) action(); }; return <button onClick={handleClick}>Perform Action</button>; } """ ## 3. Preventing Cross-Site Request Forgery (CSRF) CSRF attacks allow attackers to perform actions on behalf of an authenticated user without their knowledge. ### 3.1. Using CSRF Tokens Include a unique, unpredictable token in each state-changing request. (Typically handled at the backend, consumed by the frontend). * **Do This:** Implement CSRF protection on the server-side by generating and validating CSRF tokens. Store the CSRF token in a secure cookie or in the session. * **Don't Do This:** Omit CSRF protection from your application, as it leaves your users vulnerable to CSRF attacks. **Why:** CSRF tokens ensure that the request originated from your application, not from a malicious website. **Example Flow:** 1. **Server:** Generates a CSRF token and sends it to the client (e.g., as a cookie or within the initial HTML). 2. **Client (React):** Includes the CSRF token in every state-changing request (e.g., POST, PUT, DELETE) as a header or in the request body. 3. **Server:** Validates the CSRF token on each request. If the token is missing or invalid, the request is rejected. **Code Example (React - Including CSRF token in request):** """javascript import React, { useState, useEffect } from 'react'; function MyForm() { const [csrfToken, setCsrfToken] = useState(''); useEffect(() => { // Fetch the CSRF token from an endpoint or from a cookie const fetchCsrfToken = async () => { const response = await fetch('/api/csrf'); const data = await response.json(); setCsrfToken(data.csrfToken); }; fetchCsrfToken(); }, []); const handleSubmit = async (event) => { event.preventDefault(); const data = { //Form data here } const response = await fetch('/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken, // Include CSRF token in the header }, body: JSON.stringify(data), }); // Handle the response }; return ( <form onSubmit={handleSubmit}> {/* Form inputs */} <button type="submit">Submit</button> </form> ); } """ * **Important Considerations:** * Ensure that the server-side implementation correctly generates, stores, and validates CSRF tokens. * Protect the CSRF token from being leaked or stolen (e.g., using HTTPS, secure cookies). * Synchronizer Token Pattern or Double Submit Cookie are common implementation approaches. ### 3.2. Using "SameSite" Cookie Attribute Set the "SameSite" attribute on cookies to control when they are sent with cross-site requests. * **Do This:** Set the "SameSite" attribute to "Strict" or "Lax" to prevent cookies from being sent with cross-site requests originated from other sites. * **Don't Do This:** Leave the "SameSite" attribute unspecified, as it defaults to "None" (in some browsers), increasing the risk of CSRF attacks. **Why:** The "SameSite" attribute helps prevent CSRF attacks by restricting when cookies are sent with cross-site requests, controlling the context under which the browser sends the cookie. **Example (Server-Side - Setting SameSite attribute):** """ //Example using node.js and express res.cookie('sessionid', 'your_session_id', { sameSite: 'Strict', // or 'Lax' secure: true, // Requires HTTPS httpOnly: true, // Prevents client-side JavaScript access }); """ * "Strict": The cookie is only sent with requests originating from the same site. Offers the strongest protection but might break some legitimate cross-site linking scenarios. * "Lax": The cookie is sent with same-site requests and top-level cross-site requests initiated by a GET method. Offers a balance between security and usability. * "None": The cookie is sent with all requests, regardless of origin. Requires the "Secure" attribute to be set. Consider this option carefully. ## 4. Authentication and Authorization Properly authenticate and authorize users to control access to sensitive data and functionality. ### 4.1. Secure Password Handling Never store passwords in plain text! * **Do This:** Use a strong hashing algorithm (e.g., bcrypt, Argon2) to hash passwords before storing them in the database. Use a salt value unique to each password. * **Don't Do This:** Store passwords in plain text or use weak hashing algorithms (e.g., MD5, SHA1). **Why:** Hashing algorithms make it difficult for attackers to recover the original passwords if the database is compromised. Using a good password policy and enforcing it also improves security. ### 4.2. Multi-Factor Authentication (MFA) Implement MFA to add an extra layer of security to the authentication process. * **Do This:** Encourage or enforce MFA for users, using methods like OTP (One-Time Password) via SMS, authenticator apps, or hardware tokens. * **Don't Do This:** Rely solely on username and password for authentication, as it's vulnerable to password breaches. **Why:** MFA makes it significantly harder for attackers to gain unauthorized access to user accounts, even if they have obtained the user's password. ### 4.3. Secure Session Management Manage user sessions securely to prevent session hijacking and other session-related attacks. * **Do This:** Use strong, randomly generated session IDs. Implement session timeouts and automatic session invalidation after a period of inactivity. Regenerate session IDs after authentication to prevent session fixation attacks. Store session data securely on the server-side. * **Don't Do This:** Use predictable session IDs, store session data on the client-side, or omit session timeouts. **Why:** Secure session management helps prevent attackers from hijacking user sessions and gaining unauthorized access to user accounts. ### 4.4. JSON Web Tokens (JWT) Implement best practices for securing JSON Web Tokens (JWTs) if used for authentication/authorization. While seemingly client-side, the implications dictate security considerations. * **Do This:** * Use strong cryptographic algorithms (e.g., HS256, RS256) to sign JWTs. * Store JWTs securely (e.g., in HTTP-only cookies or in-memory). **Avoid** storing them in "localStorage" due to XSS risks. * Set appropriate expiration times for JWTs to limit the lifetime of the token. * Implement token revocation mechanisms to invalidate compromised tokens. * Validate JWTs on the server-side for every protected resource. * **Don't Do This:** * Use weak or no signature algorithms (e.g., "alg: none"). * Store JWTs insecurely (e.g., in "localStorage"). * Use excessively long expiration times. * Omit JWT validation on the server-side. **Why:** JWTs can be a convenient way to implement authentication and authorization, but they require careful handling to avoid security vulnerabilities. **Code Example (React - Storing JWT in HTTP-only cookie):** The following code example demonstrates how a JWT could be obtained and then the instruction to store on the server with httpOnly. Note: the token is being saved and not directly being used by the React Application. """javascript // Example React, after successful login const handleLoginSuccess = async (token) => { //Instead of saving the token on the client-side, typically you want to send the token to the backend //and then have the backend save it as an HTTP-ONLY cookie //This helps protects against XSS attacks const response = await fetch('/api/set-cookie', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({token}), }); const data = await response.json(); if(data.success){ //Redirect or show message here } } """ * **Note:** This example shows the React code for receiving a token after a successful login. Token handling (storage, validation) *primarily* happens server-side! This React code sends the token to the backend to create the secure cookie. ## 5. Secure Data Handling Protect sensitive data throughout its lifecycle. ### 5.1. Data Encryption Encrypt sensitive data both in transit (using HTTPS) and at rest (in the database). * **Do This:** * Use HTTPS to encrypt all communication between the client and server. * Encrypt sensitive data in the database using appropriate encryption algorithms. * Protect encryption keys securely (e.g., using a hardware security module or key management system). * **Don't Do This:** * Transmit sensitive data over unencrypted channels (e.g., HTTP). * Store sensitive data in plain text in the database. * Hardcode encryption keys in the source code or store them insecurely. **Why:** Encryption protects sensitive data from being intercepted or accessed by unauthorized parties. ### 5.2. Input Validation Validate all user inputs on both the client-side and the server-side to prevent injection attacks and data corruption. * **Do This:** * Implement client-side validation to provide immediate feedback to the user and prevent invalid data from being sent to the server. * Perform server-side validation to ensure that all data is valid and safe before storing it in the database. * Use appropriate validation techniques based on the type of data being validated (e.g., regular expressions, data type checks, range checks). * **Don't Do This:** * Rely solely on client-side validation, as it can be bypassed by attackers. * Store invalid or unsafe data in the database. **Why:** Input validation helps prevent attackers from injecting malicious code or manipulating data to compromise the application's security or integrity. **Code Example (Client-side Input Validation using React Hooks):** """jsx import React, { useState } from 'react'; function RegistrationForm() { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [usernameError, setUsernameError] = useState(''); const [passwordError, setPasswordError] = useState(''); const validateForm = () => { let isValid = true; if (username.length < 5) { setUsernameError('Username must be at least 5 characters long.'); isValid = false; } else { setUsernameError(''); } if (password.length < 8) { setPasswordError('Password must be at least 8 characters long.'); isValid = false; } else { setPasswordError(''); } return isValid; }; const handleSubmit = (event) => { event.preventDefault(); if (validateForm()) { // Submit the form data to the server console.log('Form is valid, submitting...'); } else { console.log('Form is invalid, please correct the errors.'); } }; return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="username">Username:</label> <input type="text" id="username" value={username} onChange={(e) => setUsername(e.target.value)} /> {usernameError && <div className="error">{usernameError}</div>} </div> <div> <label htmlFor="password">Password:</label> <input type="password" id="password" value={password} onChange={(e) => setPassword(e.target.value)} /> {passwordError && <div className="error">{passwordError}</div>} </div> <button type="submit">Register</button> </form> ); } """ ### 5.3. Rate Limiting Implement rate limiting to prevent brute-force attacks and protect against denial-of-service attacks. * **Do This:** * Limit the number of requests that a user can make within a given time period. * Implement rate limiting on sensitive endpoints, such as login, password reset, and API endpoints. * Use appropriate rate limiting algorithms, such as token bucket or leaky bucket. * **Don't Do This:** * Omit rate limiting from your application, as it leaves you vulnerable to brute-force and denial-of-service attacks. * Use excessively high rate limits, as they may not effectively prevent attacks. **Why:** Rate limiting helps prevent attackers from overwhelming the application with requests and compromising its availability or security. (This is almost always a server-side implementation). ## 6. Dependency Management Securely manage project dependencies to prevent vulnerabilities from third-party libraries. ### 6.1. Use a Package Manager Use a package manager like npm or yarn to manage project dependencies and ensure that you are using the correct versions. * **Do This:** Use npm or yarn to install and manage project dependencies. * **Don't Do This:** Manually download and install dependencies, as this can lead to version conflicts and security vulnerabilities. **Why:** Package managers help you keep track of project dependencies and ensure that you are using the correct versions, reducing the risk of known vulnerabilities. ### 6.2. Regularly Update Dependencies Regularly update project dependencies to patch known security vulnerabilities. * **Do This:** Use tools like "npm audit" or "yarn audit" to identify security vulnerabilities in your dependencies and update them to the latest versions. * **Don't Do This:** Ignore security vulnerabilities in your dependencies, as they can be exploited by attackers. **Why:** Regularly updating dependencies ensures that you have the latest security patches, reducing the risk of known vulnerabilities. ### 6.3. Use Dependency Scanning Tools Use dependency scanning tools to automatically identify and report security vulnerabilities in your dependencies. * **Do This:** Integrate dependency scanning tools into your development workflow to automatically identify and report security vulnerabilities in your dependencies. * **Don't Do This:** Rely solely on manual dependency reviews, as they are time-consuming and prone to errors. **Why:** Dependency scanning tools can help you identify security vulnerabilities in your dependencies more quickly and accurately, allowing you to address them proactively. ## 7. Security Headers These headers provide an extra layer of security by instructing the browser on how to behave when handling your site's content. ### 7.1 Content Security Policy (CSP) * **Do This**: Implement a strict CSP that defines the sources from which the browser is allowed to load resources. * **Don't Do This**: Use a very permissive or no CSP, as this opens up your application to XSS attacks. """ Content-Security-Policy: default-src 'self'; script-src 'self' https://apis.google.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; """ ## 8. React-Specific Security Considerations ### 8.1. Server-Side Rendering (SSR) Considerations When using Server-Side Rendering (SSR), be extra careful with data handling to prevent XSS vulnerabilities. Make sure data rendered on the server is properly sanitized. SSR opens React applications to an entirely new scope of vulnerabilities that a traditional SPA does not have. All previously mentioned principles are vital in an SSR application. ### 8.2 Avoid Leaking Sensitive Information to the Client * Only render data necessary for presenting the user interface to the client. Avoid hydrating the client with properties that have no use and are potentially dangerous. * When logging in the client, be careful not to leak PII or other sensitive data that can be leaked in production in the client environment console. ### 8.3 Third-party Libraries Security * Always be careful about using third-party libraries. They can cause security vulnerabilities. Be aware of the components or tools that you are using. Make sure to audit the licenses properly. * When using a new library, it is a good idea to scan the code for vulnerabilities using tools such as Snyk or SonarQube.
# Testing Methodologies Standards for React This document outlines the coding standards for testing practices within React projects. It serves as a guide for developers to write robust, maintainable, and testable React code. It also serves as context for AI coding assistants. ## 1. General Testing Philosophy ### 1.1. Test Pyramid **Do This:** Advocate for a balanced test pyramid: many unit tests, fewer integration tests, and even fewer end-to-end (E2E) tests. **Don't Do This:** Rely solely on end-to-end tests which are slow and difficult to maintain. **Why:** Unit tests provide fast feedback on individual components. Integration tests verify the interaction between multiple components. End-to-end tests validate the whole system. A balanced approach prevents over-reliance on slow, brittle E2E tests while ensuring thorough coverage. ### 1.2. Test-Driven Development (TDD) **Do This:** Consider practicing TDD by writing tests before writing the component code itself. **Don't Do This:** Write tests as an afterthought. **Why:** TDD helps clarify requirements, encourages modular design, and ensures testability. It helps avoid writing code that is difficult or impossible to test. However, strictly enforced TDD is not always necessary and can be less pragmatic for UI-centric work. Consider behavior-driven development (BDD) approaches as a more flexible alternative. ### 1.3. Code Coverage **Do This:** Aim for high code coverage (80%+) for critical business logic and components. Use code coverage tools to measure effectiveness. **Don't Do This:** Blindly chase 100% code coverage. Focus on testing important functionality and user interactions. **Why:** Code coverage provides a metric to assess the extent to which your code is being tested. Strive for meaningful tests, not just lines of code covered. Coverage should be used to identify gaps in your testing strategy. ### 1.4. Test Doubles (Mocks, Stubs, Spies) **Do This:** Use mocks, stubs, and spies judiciously to isolate units under test and control dependencies. **Don't Do This:** Overuse mocks, as it can lead to tests that are too tightly coupled to the implementation details. **Why:** Test doubles allow you to focus on the behavior of the unit under test in isolation. Choose the appropriate type of test double based on what you need to verify: * **Stubs:** Provide predefined responses to function calls. * **Mocks:** Verify that specific functions are called with expected arguments. * **Spies:** Record the arguments and return values of function calls. ## 2. Unit Testing ### 2.1. Component Isolation **Do This:** Write unit tests for individual React components in isolation. Mock or stub out external dependencies, such as API calls or context values. **Don't Do This:** Unit test components with dependencies that are difficult to manage, leading to brittle and slow tests. **Why:** Isolated tests are faster, more reliable, and easier to debug. ### 2.2. Testing Component Behavior **Do This:** Focus on testing the component's behavior, such as rendering the correct output, handling user interactions, and updating its state. **Don't Do This:** Test internal implementation details, as it can lead to tests that break easily when the implementation changes. **Why:** Testing behavior ensures that the component meets its functional requirements, regardless of how it is implemented. ### 2.3. Libraries **Do This:** Use testing libraries like React Testing Library and Jest for React unit tests. Use Mock Service Worker (MSW) for mocking API calls. **Don't Do This:** Use enzyme library. This library is no longer maintained and officially not supported by react. **Why:** React Testing Library encourages testing components from the user's perspective, focusing on what the user sees and interacts with. Jest provides a comprehensive testing framework with features like mocking, assertions, and code coverage. MSW enables mocking API calls in the browser or Node.js, improving test reliability. **Example:** """jsx // Component.jsx import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); const increment = () => { setCount(count + 1); }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> </div> ); } export default Counter; """ """jsx // Component.test.jsx import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import Counter from './Counter'; describe('Counter Component', () => { it('should render the initial count', () => { render(<Counter />); const countElement = screen.getByText(/Count: 0/i); expect(countElement).toBeInTheDocument(); }); it('should increment the count when the button is clicked', () => { render(<Counter />); const incrementButton = screen.getByRole('button', { name: /Increment/i }); fireEvent.click(incrementButton); const countElement = screen.getByText(/Count: 1/i); expect(countElement).toBeInTheDocument(); }); }); """ ### 2.4. Testing Hooks **Do This:** Use "@testing-library/react-hooks" to test custom React hooks in isolation. **Don't Do This:** Test hooks within a component or in a way that depends on the component's implementation. **Why:** Testing hooks in isolation ensures that they behave correctly, independent of any specific component. **Example:** """jsx // useCounter.js import { useState } from 'react'; function useCounter(initialValue = 0) { const [count, setCount] = useState(initialValue); const increment = () => { setCount(count + 1); }; return { count, increment }; } export default useCounter; """ """jsx // useCounter.test.js import { renderHook, act } from '@testing-library/react-hooks'; import useCounter from './useCounter'; describe('useCounter Hook', () => { it('should initialize the count to the initial value', () => { const { result } = renderHook(() => useCounter(10)); expect(result.current.count).toBe(10); }); it('should increment the count', () => { const { result } = renderHook(() => useCounter(0)); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); }); }); """ ### 2.5. Snapshot Testing (Use Sparingly) **Do This:** Use snapshot testing only for components with complex rendering output that is difficult to assert manually. **Don't Do This:** Overuse snapshot testing for all components, as it can lead to tests that are difficult to maintain and less effective at catching bugs. Avoid snapshot tests of large components with frequently changing data. **Why:** Snapshot testing can be useful for quickly verifying that a component's rendering output has not changed unexpectedly. However, it should be used sparingly and with caution, as it can easily mask underlying issues. Prefer property-based testing where feasible. ## 3. Integration Testing ### 3.1. Component Interactions **Do This:** Write integration tests to verify the interaction between multiple React components. **Don't Do This:** Rely only on unit tests or end-to-end tests, which may not catch integration issues. **Why:** Integration tests ensure that components work together correctly and that data flows between them as expected. ### 3.2. Testing State Management **Do This:** Integrate with state management libraries (Redux, Zustand, Context API) for testing component interactions. **Don't Do This:** Mock the internal state management logic, which can lead to tests that are too tightly coupled to the implementation. **Why:** Testing with the actual state management helps to catch subtle issues related to state updates and data flow. **Example (using React Context):** """jsx // ThemeContext.jsx import React, { createContext, useState } from 'react'; export 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> ); } export default ThemeProvider; """ """jsx // ThemeToggler.jsx import React, { useContext } from 'react'; import { ThemeContext } from './ThemeContext'; function ThemeToggler() { const { theme, toggleTheme } = useContext(ThemeContext); return ( <button onClick={toggleTheme}> Toggle Theme ({theme}) </button> ); } export default ThemeToggler; """ """jsx // ThemeContext.test.jsx import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import ThemeProvider, { ThemeContext } from './ThemeContext'; import ThemeToggler from './ThemeToggler'; describe('ThemeContext Integration', () => { it('should toggle the theme when the button is clicked', () => { render( <ThemeProvider> <ThemeToggler /> </ThemeProvider> ); const toggleButton = screen.getByRole('button', { name: /Toggle Theme/i }); expect(toggleButton).toHaveTextContent('Toggle Theme (light)'); fireEvent.click(toggleButton); expect(toggleButton).toHaveTextContent('Toggle Theme (dark)'); fireEvent.click(toggleButton); expect(toggleButton).toHaveTextContent('Toggle Theme (light)'); }); }); """ ### 3.3. Testing API Interactions **Do This:** Use MSW to mock API endpoints and verify that components make the correct requests and handle the responses correctly. **Don't Do This:** Make real API calls in integration tests, as it can lead to flaky and unreliable tests. **Why:** Mocking API calls ensures that integration tests are isolated and can be run consistently in any environment. MSW allows intercepting network requests at the network level, providing a realistic and reliable mocking solution. ## 4. End-to-End (E2E) Testing ### 4.1. High-Level User Flows **Do This:** Write E2E tests for critical user flows, such as login, signup, and checkout. **Don't Do This:** Write E2E tests for every single component or interaction, as it can lead to a large and unmaintainable test suite. **Why:** E2E tests ensure that the entire application works correctly from the user's perspective. They validate that all the components and services are integrated properly. ### 4.2. Tooling **Do This:** Use tools like Cypress, Playwright, or Selenium for E2E testing. **Don't Do This:** Write your own E2E testing framework, as it can be time-consuming and difficult to maintain. **Why**: These tools are designed for E2E testing and provide features such as test runners, assertions, and debugging tools. Cypress and Playwright are recommended due to their speed, reliability, and modern features like auto-waiting and cross-browser testing. ### 4.3. Test Environment **Do This:** Run E2E tests in a dedicated test environment that is as close as possible to the production environment. **Don't Do This:** Run E2E tests against the production environment, as it can be risky and disruptive. **Why:** A dedicated test environment ensures that E2E tests are isolated and do not interfere with the production environment. ### 4.4. Data Setup and Teardown **Do This:** Set up the necessary data before each E2E test and clean up the data after the test is finished. **Don't Do This:** Rely on existing data in the test environment, as it can lead to tests that are flaky and unreliable. **Why:** Proper data setup and teardown ensure that E2E tests are repeatable and consistent. **Example (Cypress):** """javascript // cypress/e2e/login.cy.js describe('Login Flow', () => { beforeEach(() => { // Visit the login page before each test cy.visit('/login'); }); it('should successfully log in with valid credentials', () => { // Get the email and password input fields and type in the credentials cy.get('input[name="email"]').type('test@example.com'); cy.get('input[name="password"]').type('password'); // Get the login button and click it cy.get('button[type="submit"]').click(); // Assert that the user is redirected to the dashboard cy.url().should('include', '/dashboard'); // Assert that the welcome message is displayed cy.contains('Welcome, test@example.com').should('be.visible'); }); it('should display an error message with invalid credentials', () => { // Get the email and password input fields and type in invalid credentials cy.get('input[name="email"]').type('invalid@example.com'); cy.get('input[name="password"]').type('wrongpassword'); // Get the login button and click it cy.get('button[type="submit"]').click(); // Assert that the error message is displayed cy.contains('Invalid email or password').should('be.visible'); }); }); """ ## 5. Accessibility Testing ### 5.1. ARIA Attributes **Do This:** Use ARIA attributes to provide semantic information to assistive technologies, such as screen readers. **Don't Do This:** Rely solely on visual cues to convey information. **Why:** ARIA attributes improve the accessibility of React components for users with disabilities. It allows assistive technologies to understand the structure and purpose of the UI. ### 5.2. Semantic HTML **Do This:** Use semantic HTML elements, such as "<header>", "<nav>", "<main>", "<footer>", "<article>", and "<aside>", to structure the content of your React components. **Don't Do This:** Use "<div>" elements for everything. **Why:** Semantic HTML elements provide semantic information to assistive technologies and improve the overall accessibility of the application. ### 5.3. Testing Tools **Do This:** Use accessibility testing tools like Axe, WAVE, or eslint-plugin-jsx-a11y to identify and fix accessibility issues. **Don't Do This:** Ignore accessibility issues. **Why:** Accessibility testing tools can help you identify common accessibility issues and provide guidance on how to fix them. "eslint-plugin-jsx-a11y" can catch accessibility issues during development, while Axe and WAVE can be used to test the accessibility of rendered components in the browser. **Example (using eslint-plugin-jsx-a11y):** """javascript // .eslintrc.js module.exports = { extends: [ 'eslint:recommended', 'plugin:react/recommended', 'plugin:jsx-a11y/recommended', // Enable accessibility rules ], parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 2018, sourceType: 'module', }, plugins: ['react', 'jsx-a11y'], rules: { // Add or override rules as needed }, settings: { react: { version: 'detect', }, }, }; """ ## 6. Performance Testing ### 6.1. Profiling **Do This:** Use React Profiler tool to identify performance bottlenecks in React components. **Don't Do This:** Guess at performance issues without profiling. **Why:** Profiling provides insights into which components are taking the most time to render and update. It helps identify areas for optimization. ### 6.2. Memoization **Do This:** Use "React.memo" to memoize functional components and prevent unnecessary re-renders. Use "useMemo" and "useCallback" hooks to memoize values and functions. **Don't Do This:** Overuse memoization, as it can add unnecessary complexity and overhead. **Why:** Memoization can improve performance by preventing components from re-rendering when their props have not changed. ### 6.3. Lazy Loading **Do This:** Use "React.lazy" to lazy-load components that are not immediately needed. **Don't Do This:** Load all components upfront, as it can slow down the initial page load. **Why:** Lazy loading can improve the initial page load time by deferring the loading of non-critical components. ### 6.4. Virtualization **Do This:** Use virtualization libraries like "react-window" or "react-virtualized" to efficiently render large lists and tables. **Don't Do This:** Render all items in a large list or table at once, as it can lead to poor performance. **Why:** Virtualization libraries render only the visible items in a large list or table, improving performance and reducing memory usage. ## 7. Security Testing ### 7.1. Input Validation **Do This:** Validate all user inputs on both the client-side and the server-side to prevent vulnerabilities such as cross-site scripting (XSS) and SQL injection. **Don't Do This:** Trust user inputs without validation. **Why:** Input validation helps prevent malicious users from injecting harmful code or data into the application. ### 7.2. Output Encoding **Do This:** Encode all user-generated content before rendering it in the browser to prevent XSS attacks. **Don't Do This:** Render user-generated content directly without encoding it. **Why:** Output encoding helps prevent XSS attacks by escaping special characters that could be interpreted as code. ### 7.3. Dependency Management **Do This:** Regularly update dependencies to patch security vulnerabilities. Use tools like "npm audit" or "yarn audit" to identify vulnerable packages. **Don't Do This:** Ignore security warnings from dependency management tools. **Why:** Regularly updating dependencies helps protect the application from known security vulnerabilities. For example you can add "simple-git-hooks" to your project in order to make sure that security audits pass before you commit any code. ### 7.4. Security Linters **Do This:** Use security linters, such as ESLint with security-related plugins, to identify potential security vulnerabilities in the code. **Don't Do This:** Ignore security linter warnings. **Why:** Security linters can help you identify common security vulnerabilities and provide guidance on how to fix them. By following these coding standards, developers can write robust, maintainable, and testable React code that meets the highest quality standards. These standards are designed to improve code quality, reduce bugs, and enhance the overall development process, as well as enhance the effectiveness of AI tools used by developers.
# 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.