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