projectrules.ai

react-query Best Practices

react-queryreactbest-practicesperformancetesting

Description

This rule enforces best practices for using react-query in React applications, covering code organization, performance, security, and testing.

Globs

**/*.{js,jsx,ts,tsx}
---
description: This rule enforces best practices for using react-query in React applications, covering code organization, performance, security, and testing.
globs: **/*.{js,jsx,ts,tsx}
---

# react-query Best Practices

This document outlines the best practices for using react-query in React applications, covering various aspects such as code organization, performance considerations, security, and testing.

## 1. Code Organization and Structure

### 1.1 Directory Structure Best Practices

*   **Feature-based Organization:** Group react-query hooks and related components within feature-specific directories.  This improves modularity and maintainability.

    src/
    ├── features/
    │   ├── users/
    │   │   ├── components/
    │   │   │   ├── UserList.tsx
    │   │   │   └── UserDetails.tsx
    │   │   ├── hooks/
    │   │   │   ├── useUsersQuery.ts
    │   │   │   └── useCreateUserMutation.ts
    │   │   ├── api/
    │   │   │   └── usersApi.ts
    │   │   └── types/
    │   │       └── user.ts
    │   ├── products/
    │   └── ...
    ├── ...


*   **Dedicated API Service Layer:** Abstract API interaction logic into separate modules.  This allows for easier testing and decoupling of components from specific API implementations. Consider using a dedicated `api` directory within each feature.

### 1.2 File Naming Conventions

*   **Consistent Naming:** Follow a consistent naming convention for react-query hooks.  Prefix hooks with `use` and postfix with `Query` or `Mutation` to clearly indicate their purpose (e.g., `usePostsQuery`, `useUpdatePostMutation`).

*   **Descriptive Names:** Use descriptive names for files and variables to improve code readability.  For example, `useFetchUsersQuery.ts` is more informative than `useUsers.ts`.

### 1.3 Module Organization

*   **Custom Hooks for Reusability:** Encapsulate react-query logic within custom hooks to promote reusability and separation of concerns.

    typescript
    // src/features/users/hooks/useUsersQuery.ts
    import { useQuery } from '@tanstack/react-query';
    import { fetchUsers } from '../api/usersApi';

    export const useUsersQuery = () => {
      return useQuery('users', fetchUsers);
    };


*   **Separate Query and Mutation Files:** Organize queries and mutations into separate files or modules. This enhances code readability and maintainability.

### 1.4 Component Architecture

*   **Presentational and Container Components:** Separate presentational components (UI) from container components (data fetching and state management).  This improves testability and reusability.

*   **Composition:** Use component composition to build complex UIs from smaller, reusable components.  Leverage React Context for shared state when appropriate.

### 1.5 Code Splitting Strategies

*   **Route-Based Splitting:** Split your application into separate bundles based on routes.  This reduces the initial load time and improves perceived performance. React.lazy and React.Suspense can assist with this.

*   **Component-Based Splitting:** Split large components into smaller chunks that can be loaded on demand.  This can improve the performance of individual pages or components.

## 2. Common Patterns and Anti-patterns

### 2.1 Design Patterns Specific to react-query

*   **Custom Hooks for Data Fetching:** As highlighted earlier, encapsulating react-query logic within custom hooks. This promotes reusability and separation of concerns. These hooks will typically return the result of a `useQuery` or `useMutation` call.

*   **Optimistic Updates:** Implement optimistic updates to improve perceived performance.  This involves updating the UI before the API request completes, and then reverting the changes if the request fails.  react-query provides utilities like `onMutate` to handle this.

*   **Pessimistic Updates:** Update the UI only after a successful response from the API. Simpler to implement but provides a less snappy user experience.

### 2.2 Recommended Approaches for Common Tasks

*   **Prefetching Data:** Prefetch data for routes or components that the user is likely to visit next.  This can significantly improve the user experience.  Use `queryClient.prefetchQuery`.

*   **Pagination and Infinite Scrolling:** Implement pagination and infinite scrolling to handle large datasets efficiently.  react-query provides hooks like `useInfiniteQuery` to simplify this.

*   **Dependent Queries:** Fetch data based on the result of a previous query. Use the `enabled` option in `useQuery` to conditionally execute queries.

    typescript
    const { data: user } = useQuery(['user', userId], () => fetchUser(userId));

    const { data: posts } = useQuery(['posts', user?.id], () => fetchPosts(user.id), {
      enabled: !!user,
    });


### 2.3 Anti-patterns and Code Smells to Avoid

*   **Directly Calling API in Components:** Avoid making API calls directly within components.  This makes testing difficult and tightly couples components to specific API implementations.

*   **Ignoring Error Handling:** Always handle errors properly.  Display user-friendly error messages and provide options for retrying requests.

*   **Over-fetching Data:** Fetch only the data that is required by the component.  Use GraphQL or API query parameters to reduce the amount of data transferred.

*   **Deeply Nested Queries:** Avoid deeply nesting queries, as this can lead to performance issues and make the code difficult to understand. Consider combining queries or using a different approach.

### 2.4 State Management Best Practices

*   **Local vs. Global State:** Determine whether data should be stored in local component state or in a global state management solution.  Use local state for component-specific data and global state for data that needs to be shared across multiple components.

*   **react-query as a State Manager:** Leverage react-query's built-in caching and state management capabilities.  Avoid using external state management libraries for data that is already managed by react-query.

### 2.5 Error Handling Patterns

*   **Centralized Error Handling:** Implement centralized error handling to provide consistent error messages and logging.

*   **Retry Logic:** Implement retry logic to automatically retry failed requests. react-query provides options for configuring retry behavior.

*   **Error Boundaries:** Use Error Boundaries to catch errors that occur during rendering. This prevents the entire application from crashing.

## 3. Performance Considerations

### 3.1 Optimization Techniques

*   **Query Invalidation:** Invalidate queries when data changes. This ensures that the UI is always up-to-date.

*   **Stale-While-Revalidate:** Use the `staleTime` and `cacheTime` options to configure how long data should be considered fresh.  `staleWhileRevalidate` allows the UI to display cached data while fetching fresh data in the background.

*   **Window Focus Refetching:**  Configure refetching on window focus to keep data fresh when the user switches back to the application.

*   **Polling/Refetch Intervals:** Use `refetchInterval` to periodically refetch data.  This is useful for data that changes frequently.

### 3.2 Memory Management

*   **Query Cache Management:** Understand how react-query manages its cache.  Configure the `cacheTime` option to control how long data is stored in the cache.

*   **Garbage Collection:** Ensure that unused queries are garbage collected properly.  Use the `gcTime` option to configure how long inactive queries should be kept in the cache.

### 3.3 Rendering Optimization

*   **Memoization:** Use `React.memo` to prevent unnecessary re-renders of components.  This is especially important for components that display data fetched from react-query.

*   **Virtualization:** Use virtualization techniques (e.g., `react-window`, `react-virtualized`) to efficiently render large lists of data.

### 3.4 Bundle Size Optimization

*   **Tree Shaking:** Ensure that your build process is configured for tree shaking.  This removes unused code from the final bundle.

*   **Code Splitting:** As mentioned earlier, use code splitting to reduce the initial load time.

### 3.5 Lazy Loading Strategies

*   **Lazy Load Components:** Use `React.lazy` to lazy load components that are not immediately needed.

*   **Lazy Load Data:** Fetch data only when it is needed.  Use dependent queries to fetch data based on user interactions.

## 4. Security Best Practices

### 4.1 Common Vulnerabilities and How to Prevent Them

*   **Cross-Site Scripting (XSS):** Sanitize user input to prevent XSS attacks. Use a library like DOMPurify to sanitize HTML.

*   **Cross-Site Request Forgery (CSRF):** Implement CSRF protection to prevent attackers from performing actions on behalf of the user. Use a library or framework that provides CSRF protection.

*   **Injection Attacks:** Protect against injection attacks by validating user input and using parameterized queries.

### 4.2 Input Validation

*   **Client-Side Validation:** Implement client-side validation to provide immediate feedback to the user.

*   **Server-Side Validation:** Always validate user input on the server-side to prevent malicious data from being stored in the database.

### 4.3 Authentication and Authorization Patterns

*   **JSON Web Tokens (JWT):** Use JWTs for authentication. Store the JWT in a secure cookie or in local storage (with caution). Use `httpOnly` flag on cookies containing JWTs when possible to prevent client-side script access.

*   **Role-Based Access Control (RBAC):** Implement RBAC to control access to different parts of the application. Use middleware or custom hooks to check user roles.

### 4.4 Data Protection Strategies

*   **Encryption:** Encrypt sensitive data at rest and in transit. Use HTTPS to encrypt data in transit. Encrypt sensitive data in the database.

*   **Data Masking:** Mask sensitive data in logs and reports. This prevents sensitive data from being exposed to unauthorized users.

### 4.5 Secure API Communication

*   **HTTPS:** Use HTTPS for all API communication. This encrypts data in transit and prevents eavesdropping.

*   **API Rate Limiting:** Implement API rate limiting to prevent abuse.

*   **CORS:** Configure CORS properly to prevent cross-origin requests from unauthorized domains.

## 5. Testing Approaches

### 5.1 Unit Testing Strategies

*   **Test Custom Hooks:** Unit test custom react-query hooks to ensure they are working correctly.  Mock the API calls using libraries like `msw` (Mock Service Worker).

*   **Test Components in Isolation:** Unit test components in isolation to ensure they render correctly and handle user interactions properly. Use libraries like `react-testing-library`.

### 5.2 Integration Testing

*   **Test Data Flow:** Integration test the data flow between components and APIs.  Verify that data is fetched correctly and displayed properly.

*   **Test Error Handling:** Integration test error handling scenarios to ensure that errors are handled properly.

### 5.3 End-to-End Testing

*   **Simulate User Interactions:** Use end-to-end testing frameworks like Cypress or Playwright to simulate user interactions and verify that the application is working correctly from the user's perspective.

*   **Test Critical Paths:** Focus on testing critical user flows, such as login, registration, and checkout.

### 5.4 Test Organization

*   **Colocate Tests with Components:** Colocate tests with the components they are testing.  This makes it easier to find and maintain tests.

*   **Use Descriptive Test Names:** Use descriptive test names to clearly indicate what each test is verifying.

### 5.5 Mocking and Stubbing

*   **Mock API Calls:** Use mocking libraries like `msw` to mock API calls during testing. This allows you to test components in isolation without relying on a real API.

*   **Stub External Dependencies:** Stub external dependencies to isolate components and make tests more predictable.

## 6. Common Pitfalls and Gotchas

*   **Forgetting to Invalidate Queries:** Failing to invalidate queries when data changes can lead to stale data being displayed in the UI.

*   **Incorrect Cache Configuration:** Incorrectly configuring the `cacheTime` and `staleTime` options can lead to performance issues or stale data.

*   **Not Handling Errors Properly:** Not handling errors properly can lead to unexpected behavior and a poor user experience.

*   **Over-relying on Default Configuration:** Customizing react-query to match specific needs is essential.

*   **Ignoring Devtools:** The react-query devtools are invaluable for debugging and understanding what is happening under the hood.

## 7. Tooling and Environment

### 7.1 Recommended Development Tools

*   **VS Code:** Use VS Code with extensions like ESLint, Prettier, and TypeScript to improve developer productivity.

*   **React Developer Tools:** Use the React Developer Tools browser extension to inspect React components and state.

*   **react-query Devtools:** Use the react-query Devtools to inspect the react-query cache and track query and mutation status.

### 7.2 Build Configuration

*   **Webpack or Parcel:** Use a bundler like Webpack or Parcel to bundle your code for production. Configure the bundler for tree shaking and code splitting.

*   **Babel:** Use Babel to transpile your code to older versions of JavaScript. This ensures that your code is compatible with older browsers.

### 7.3 Linting and Formatting

*   **ESLint:** Use ESLint to enforce coding standards and prevent errors. Configure ESLint to use a popular style guide like Airbnb or Google.

*   **Prettier:** Use Prettier to automatically format your code. This ensures that your code is consistently formatted and easy to read.

### 7.4 Deployment Best Practices

*   **CDN:** Use a CDN to serve static assets. This improves performance and reduces the load on your server.

*   **Caching:** Configure caching properly on your server and CDN. This reduces the number of requests to your server and improves performance.

### 7.5 CI/CD Integration

*   **Automated Testing:** Integrate automated testing into your CI/CD pipeline. This ensures that your code is tested automatically before it is deployed.

*   **Automated Deployment:** Automate the deployment process to reduce the risk of errors and improve efficiency.