# Testing Methodologies Standards for Shadcn
This document outlines the coding standards for testing methodologies in Shadcn projects. It provides guidelines for unit, integration, and end-to-end (E2E) testing, with a focus on best practices that improve code quality, maintainability, and reliability within the Shadcn ecosystem. These guidelines are designed to inform both developers and AI coding assistants, helping to ensure consistent and high-quality testing across all Shadcn codebases.
## 1. General Testing Principles
### 1.1 Test Pyramid
**Principle:** Follow the test pyramid structure to ensure a balanced testing strategy: many unit tests, fewer integration tests, and even fewer E2E tests.
**Why:** This approach provides fast feedback through unit tests, while integration and E2E tests validate interactions between components and the overall application. The cost and maintenance increase as you move up the pyramid.
**Do This:**
* Write comprehensive unit tests for individual components and functions.
* Create integration tests to verify component interactions and data flow.
* Use E2E tests for critical user flows and system-level validation.
**Don't Do This:**
* Rely solely on E2E tests, as they are slower and more brittle.
* Neglect unit tests in favor of integration or E2E tests.
### 1.2 Test-Driven Development (TDD)
**Principle:** Consider adopting TDD principles by writing tests before implementing the actual code.
**Why:** TDD drives design by forcing you to think about the component's interface and behavior before implementation. It improves code coverage and leads to more testable and maintainable code.
**Do This:**
* Write a failing test that defines the desired functionality.
* Implement the code to make the test pass.
* Refactor the code while ensuring all tests still pass.
**Don't Do This:**
* Write tests only after the code is complete.
* Skip the refactoring step, leading to potentially less maintainable code.
### 1.3 Test Isolation
**Principle:** Ensure tests are isolated from each other and external dependencies as much as possible.
**Why:** Isolation makes tests more predictable and easier to debug. It also prevents tests from interfering with each other, ensuring reliable results.
**Do This:**
* Use mocking and stubbing to isolate components from external services and dependencies.
* Reset the state of the application and environment between tests.
* Avoid shared mutable state that can affect test outcomes.
**Don't Do This:**
* Rely on external databases or APIs in unit tests without proper mocking.
* Allow tests to modify global state, leading to unpredictable results.
## 2. Unit Testing Standards
### 2.1 Scope
**Principle:** Unit tests should focus on individual components, functions, or modules in isolation.
**Why:** Isolating your components helps narrow down exactly where failures occur and allows you to test edge cases efficiently.
**Do This:**
* Test component props, state updates, and rendering logic.
* Test utility functions for correct output based on different inputs.
* Verify that event handlers trigger the expected actions.
**Don't Do This:**
* Test multiple components interacting with each other in a single unit test (that's integration testing).
* Over-test implementation details that are likely to change, instead focus on public APIs or externally visible behaviour.
### 2.2 Tools and Libraries
**Principle:** Use appropriate testing libraries and frameworks, such as Jest, Vitest, React Testing Library, or Testing Playground.
**Why:** These tools provide utilities for rendering components, simulating user interactions, and asserting expected outcomes. The React Testing Library encourages testing from the user's perspective.
**Do This:**
* Use "React Testing Library"'s "render", "screen", and "fireEvent" utilities.
* Utilize Jest or Vitest for test runners, assertions, and mocking.
* Use "msw" (Mock Service Worker) to mock API requests.
* Consider "jest-dom" for extended DOM assertion matchers.
**Don't Do This:**
* Use Enzyme, which is becoming deprecated; prefer React Testing Library.
* Rely on shallow rendering unless specifically needed; prefer full rendering for behavior testing.
### 2.3 Code Examples
**Example 1: Testing a simple Shadcn button component**
"""typescript
// components/Button.tsx (Shadcn based)
import * as React from "react"
import { cn } from "@/lib/utils"
export interface ButtonProps
extends React.ButtonHTMLAttributes {
variant?:
| "default"
| "destructive"
| "outline"
| "secondary"
| "ghost"
| null
| undefined
size?: "default" | "sm" | "lg" | "icon" | null | undefined
}
const Button = React.forwardRef(
({ className, children, variant = "default", size = "default", ...props }, ref) => {
return (
{children}
)
}
)
Button.displayName = "Button"
export { Button };
"""
"""typescript
// components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button Component', () => {
it('renders with default props', () => {
render(Click me);
const buttonElement = screen.getByRole('button', { name: 'Click me' });
expect(buttonElement).toBeInTheDocument();
});
it('applies the correct class names based on the variant prop', () => {
render(Primary Button);
const buttonElement = screen.getByRole('button', { name: 'Primary Button' });
expect(buttonElement).toHaveClass('bg-primary');
});
it('applies the correct class names based on the size prop', () => {
render(Small Button);
const buttonElement = screen.getByRole('button', { name: 'Small Button' });
expect(buttonElement).toHaveClass('h-9');
});
it('handles click events', () => {
const onClick = jest.fn();
render(Clickable Button);
const buttonElement = screen.getByRole('button', { name: 'Clickable Button' });
fireEvent.click(buttonElement);
expect(onClick).toHaveBeenCalledTimes(1);
});
it('is disabled when the disabled prop is true', () => {
render(Disabled Button);
const buttonElement = screen.getByRole('button', {name: 'Disabled Button'});
expect(buttonElement).toBeDisabled();
});
});
"""
**Example 2: Testing utility functions**
"""typescript
// lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
"""
"""typescript
// lib/utils.test.ts
import { cn } from './utils';
describe('cn utility function', () => {
it('merges class names correctly', () => {
const result = cn('class1', 'class2', 'class3');
expect(result).toBe('class1 class2 class3');
});
it('handles conditional class names', () => {
const result = cn('class1', { 'class2': true }, { 'class3': false });
expect(result).toBe('class1 class2');
});
it('overrides conflicting class names with tailwind-merge', () => {
const result = cn('mr-2', 'mr-4'); // tailwind-merge will prefer "mr-4" as it is later in the input
expect(result).toBe('mr-4');
});
});
"""
### 2.4 Anti-Patterns
* **Testing implementation details:** Focus on the component's output and behavior rather than internal implementation.
* **Skipping edge cases:** Ensure tests cover all possible input and state combinations, including error conditions.
* **Ignoring accessibility:** Use tools like "jest-axe" to verify that components are accessible.
## 3. Integration Testing Standards
### 3.1 Scope
**Principle:** Integration tests should verify interactions between multiple components or modules.
**Why:** Integration tests ensure that different parts of the application work together correctly, focusing on data flow and correct functionality when components are combined.
**Do This:**
* Test interactions between components that rely on each other.
* Verify data flow between different parts of the application.
* Test state management and data persistence.
* Specifically check interactions between UI components and backend code.
**Don't Do This:**
* Test the entire application in a single integration test; that's E2E testing.
* Over-mock component dependencies; aim for realistic interaction scenarios.
### 3.2 Tools and Libraries
**Principle:** Use libraries that help simulate user interactions and data flow in a realistic manner.
**Why:** Replicating real-world scenarios in testing helps ensure that the integrated components function as expected in the actual application
**Do This:**
* Use "React Testing Library" to simulate user interactions across multiple components.
* Use mock APIs (e.g., "msw") for realistic data fetching scenarios.
* Utilize state management libraries (e.g., Zustand, Redux Toolkit) to mock stores.
**Don't Do This:**
* Rely solely on unit tests to verify component interactions.
* Avoid mocking external endpoints completely, preventing realistic integration scenarios.
### 3.3 Code Examples
**Example: Testing a component that fetches and displays data**
"""typescript
// components/DataDisplay.tsx
import React, { useState, useEffect } from 'react';
import { useData } from './DataContext'; // Example using useContext for data fetching
function DataDisplay() {
const { data, loading, error } = useData();
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
{data.map((item: any) => (
{item.name}
))}
);
}
export default DataDisplay;
"""
"""typescript
// components/DataDisplay.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import DataDisplay from './DataDisplay';
import { DataContext } from './DataContext'; // Match context location
const mockData = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
];
const MockDataProvider = ({ children, data, loading, error }: any) => (
{children}
);
describe('DataDisplay Integration', () => {
it('displays data correctly after loading', async () => {
render(
);
await waitFor(() => {
expect(screen.getByText('Item 1')).toBeInTheDocument();
expect(screen.getByText('Item 2')).toBeInTheDocument();
});
});
it('shows a loading message while data is loading', () => {
render(
);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('displays an error message if there is an error', () => {
render(
);
expect(screen.getByText('Error: Failed to fetch data')).toBeInTheDocument();
});
});
"""
### 3.4 Anti-Patterns
* **Over-mocking:** Over-mocking component dependencies can lead to tests that do not accurately reflect real-world interactions.
* **Ignoring asynchronous behavior:** Ensure tests correctly handle asynchronous operations like data fetching and state updates.
* **Testing implementation details:** Focus on testing the component's output and behavior rather than internal implementation details.
## 4. End-to-End (E2E) Testing Standards
### 4.1 Scope
**Principle:** E2E tests should simulate real user scenarios to validate the entire application flow.
**Why:** E2E tests offer the highest confidence by ensuring that the entire system, including the UI, backend services, and database, works together correctly from the user's perspective.
**Do This:**
* Test critical user flows, such as login, signup, and checkout.
* Simulate user interactions with the UI to ensure correct behavior.
* Verify data persistence and consistency across different application states.
* Perform cross-browser testing to ensure compatibility.
**Don't Do This:**
* Use E2E tests for every small component or function; this is inefficient and leads to brittle tests.
* Rely solely on E2E tests as the primary testing strategy; balance with unit and integration tests.
### 4.2 Tools and Libraries
**Principle:** Employ reliable E2E testing frameworks and libraries that simulate user interactions in a browser environment
**Why:** Using these tools helps ensure realistic testing that replicates how users interact with your application.
**Do This:**
* Use Playwright or Cypress for browser automation and test execution.
* Utilize screenshot and video recording features for debugging and reporting.
* Use mock APIs (e.g., "msw", "nock") for controlled testing environments.
**Don't Do This:**
* Use outdated or less reliable E2E testing tools.
* Avoid taking advantage of debugging and reporting features.
### 4.3 Code Examples
**Example: Testing a login flow with Playwright**
"""typescript
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
test('successful login', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await page.waitForURL('/dashboard');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('text=Welcome')).toBeVisible();
});
test('invalid login displays error message', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'wrongpassword');
await page.click('button[type="submit"]');
await expect(page.locator('text=Invalid credentials')).toBeVisible();
});
"""
### 4.4 Anti-Patterns
* **Flaky tests:** Write robust tests that are not susceptible to timing issues or environment inconsistencies.
* **Over-reliance on UI locators:** Use stable locators (e.g., "data-testid", "aria-label") rather than brittle CSS selectors.
* **Ignoring test setup and teardown:** Properly set up and tear down the test environment to ensure consistent results.
## 5. Shadcn Specific Testing Considerations
### 5.1 Component Variant Testing
Shadcn UI often relies on component variants (using "cn" utility). Ensure your tests cover all possible variant combinations.
**Example:**
"""typescript
// Tests if the 'destructive' variant of a button renders the correct style
it('renders destructive button style', () => {
render(Delete);
const buttonElement = screen.getByRole('button', { name: 'Delete' });
expect(buttonElement).toHaveClass('bg-destructive');
});
"""
### 5.2 Accessibility Testing with Radix UI Primitives
Shadcn UI often uses Radix UI primitives, making accessibility a priority.
**Do This:**
* Use "jest-axe" or similar tools to automatically check for accessibility issues in your components.
* Write tests to ensure components have proper ARIA attributes and keyboard navigation.
**Example:**
"""typescript
import { axe, toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';
import { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogCancel, AlertDialogAction } from "@/components/ui/alert-dialog"
expect.extend(toHaveNoViolations);
it('should pass accessibility tests', async () => {
const { container } = render(
Open
Are you absolutely sure?
This action cannot be undone. This will permanently delete your account
and remove your data from our servers.
Cancel
Continue
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
"""
### 5.3 Theme and Customization Testing
Shadcn emphasizes theming and component customization. Test that these customizations are correctly applied.
**Do This:**
* If your application utilizes a custom theme, ensure that your tests render components within a ThemeProvider or similar context.
* Verify that custom class names and styles are correctly merged and applied to components.
**Example:**
*Assume usage of Next Themes*
"""typescript
import { ThemeProvider } from 'next-themes';
import { render, screen } from '@testing-library/react';
import { Button } from './Button';
describe('Button theming', () => {
it('applies custom class names correctly', () => {
render(
Themed Button
);
const buttonElement = screen.getByRole('button', { name: 'Themed Button' });
expect(buttonElement).toHaveClass('custom-button');
});
});
"""
## 6. Continuous Integration (CI)
### 6.1 Automated Test Execution
**Principle:** Integrate tests into the CI/CD pipeline to automatically run tests on every code change.
**Why:** Automated testing helps catch regressions early and ensures that code changes do not break existing functionality.
**Do This:**
* Set up CI/CD pipelines using tools like GitHub Actions, GitLab CI, or Jenkins.
* Configure the pipeline to run unit, integration, and E2E tests automatically.
* Use code coverage tools (e.g., Istanbul, Jest coverage) to track test coverage.
* Fail the build if tests fail to prevent broken code from being deployed.
**Don't Do This:**
* Manually run tests before deployment.
* Ignore failing tests in the CI/CD pipeline.
* Deploy code with low test coverage.
This document provides a comprehensive guideline for testing methodologies in Shadcn projects. Adhering to these standards will help ensure the creation of robust, maintainable, and reliable applications.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Deployment and DevOps Standards for Shadcn This document outlines the best practices and coding standards for Deployment and DevOps within Shadcn projects. It aims to guide developers in building reliable, scalable, and maintainable applications that efficiently utilize the Shadcn ecosystem. These standards should be used as context for AI coding assistants like GitHub Copilot, Cursor, and similar tools to ensure consistency and quality across the codebase. ## 1. Build Processes and CI/CD Pipelines ### 1.1. Standard: Automated Builds with CI/CD **Do This:** Implement a CI/CD pipeline for automated builds, testing, and deployment. Use tools like GitHub Actions, GitLab CI, CircleCI, or Jenkins. **Don't Do This:** Manually build and deploy changes to production. **Why:** Automation reduces human error, provides faster feedback loops, and ensures consistent deployment across environments. **Example (GitHub Actions):** """yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Install Dependencies run: npm ci - name: Build run: npm run build - name: Deploy to Vercel # Or Netlify, AWS, etc. run: vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} """ **Anti-pattern:** Manually running "npm run build" and copying the output to a server. ### 1.2. Standard: Versioning and Tagging **Do This:** Use semantic versioning (SemVer) for releases. Automatically tag Git commits with version numbers during the CI/CD process. **Don't Do This:** Use arbitrary versioning schemes or skip versioning altogether. **Why:** Semantic versioning provides clarity on the nature and scope of changes, facilitating smoother upgrades and dependency management. **Example (Versioning in "package.json"):** """json { "name": "my-shadcn-app", "version": "1.2.3", "description": "A fantastic Shadcn application", "main": "index.js", "scripts": { "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "@radix-ui/react-slot": "^1.0.2", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "lucide-react": "^0.303.0", "next": "14.0.4", "react": "18.2.0", "react-dom": "18.2.0", "tailwind-merge": "^2.2.0", "tailwindcss": "^3.4.0", "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@types/node": "20.10.6", "@types/react": "18.2.46", "@types/react-dom": "18.2.18", "autoprefixer": "^10.4.16", "eslint": "8.56.0", "eslint-config-next": "14.0.4", "postcss": "^8.4.32", "typescript": "5.3.3" } } """ **Anti-pattern:** Skipping the "npm version" command and manually updating the "package.json". ### 1.3. Standard: Environment Variables Management **Do This:** Use environment variables for configuration. Store secrets in a secure vault (like HashiCorp Vault, AWS Secrets Manager, or Vercel/Netlify environment variables). Access environment variables using a consistent mechanism. Consider using a library like "zod" with "next-safe-action" to ensure type-safe validation of environment variables. **Don't Do This:** Hardcode sensitive information in the codebase or commit it to the repository. **Why:** Properly managed environment variables allow for easy configuration changes across different environments (development, staging, production) without modifying the code. **Example (Environment Variables with Zod and next-safe-action):** First, install "next-safe-action" and "zod": "npm install next-safe-action zod" """typescript // lib/env.ts import { z } from "zod"; const envSchema = z.object({ DATABASE_URL: z.string().min(1), NEXTAUTH_SECRET: z.string().min(1), NEXTAUTH_URL: z.string().url(), GITHUB_CLIENT_ID: z.string().min(1), GITHUB_CLIENT_SECRET: z.string().min(1), }); const _env = envSchema.safeParse(process.env); if (_env.success === false) { console.error( "Invalid environment variables:\n", _env.error.format() ); throw new Error("Invalid environment variables"); } export const env = _env.data; """ """typescript // src/app/actions.ts 'use server' // Ensure this is a server action import { createSafeActionClient } from "next-safe-action"; import { z } from "zod"; import { env } from "@/lib/env"; const safeAction = createSafeActionClient(); const inputSchema = z.object({ name: z.string().min(2), email: z.string().email() }); export const submitForm = safeAction(inputSchema, async (data) => { // Access environment variables in a type-safe way console.log("DATABASE_URL: ${env.DATABASE_URL}"); console.log("User Data: ${data.name}, ${data.email}"); // Simulate database operation await new Promise((resolve) => setTimeout(resolve, 1000)); return {message: "Form submitted successfully by ${data.name}!"}; }); """ **Anti-pattern:** Directly using "process.env.API_KEY" throughout the application without validation or default values. Committing ".env" files to the repository. ### 1.4. Standard: Build Artifact Caching **Do This:** Cache build artifacts and dependencies during CI/CD to speed up build times (e.g., using "actions/cache" in GitHub Actions or similar features in other CI tools). **Don't Do This:** Reinstall dependencies and rebuild everything from scratch on every CI/CD run. **Why:** Caching significantly reduces build times, leading to faster deployment cycles and reduced resource consumption. **Example (Caching dependencies in GitHub Actions):** """yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Cache dependencies uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Install Dependencies run: npm ci - name: Build run: npm run build - name: Deploy to Vercel # Or Netlify, AWS, etc. run: vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} """ **Anti-pattern:** Not caching dependencies or build outputs, leading to slow and inefficient builds. ## 2. Production Considerations ### 2.1. Standard: Monitoring and Alerting **Do This:** Implement comprehensive monitoring and alerting for your application. Use tools like Prometheus, Grafana, Datadog, or New Relic. Monitor key metrics such as response time, error rates, CPU usage, and memory consumption. Integrate Shadcn components (progress indicators, status messages) within your monitoring dashboards. **Don't Do This:** Deploy to production without any monitoring in place. **Why:** Monitoring allows you to quickly identify and resolve issues in production, ensuring high availability and a smooth user experience. Alerting ensures you are notified promptly when something goes wrong. **Example (Basic Error Logging with Shadcn UI Feedback:)** """typescript // src/components/ErrorDisplay.tsx import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { XCircle } from "lucide-react"; // Assuming you're using Lucide interface ErrorDisplayProps { errorMessage: string; } const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ errorMessage }) => { if (!errorMessage) { return null; } return ( <Alert variant="destructive"> <XCircle className="h-4 w-4" /> <AlertTitle>Error</AlertTitle> <AlertDescription>{errorMessage}</AlertDescription> </Alert> ); }; export default ErrorDisplay; // Usage: try { // Some code that might throw an error const result = await fetchData(); if (!result) { throw new Error("Failed to fetch data"); } } catch (error:any) { console.error("Error occurred:", error); // Log the error on the server // Set state or pass to error display component setErrorMessage(error.message || "An unexpected error occurred."); // Inform user with Shadcn Alert } """ **Anti-pattern:** Relying solely on user reports to identify issues in production. ### 2.2. Standard: Logging **Do This:** Implement structured logging with appropriate levels (debug, info, warning, error). Use a logging library like Winston or Bunyan. Aggregate logs using a centralized logging system (e.g., ELK stack, Graylog, or Splunk). **Don't Do This:** Use "console.log" for all logging in production. **Why:** Centralized logging allows you to easily search, analyze, and correlate logs across different services, making it easier to diagnose and troubleshoot issues. **Example (Using Winston for structured logging):** """javascript // logger.js const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.json(), defaultMeta: { service: 'my-shadcn-app' }, transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }) ] }); if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.simple() })); } module.exports = logger; // Usage: const logger = require('./logger'); try { // Some code that might throw an error } catch (error) { logger.error('An error occurred', { error: error, message: error.message }); } """ **Anti-pattern:** Scattering "console.log" statements throughout the codebase without any structure or context. ### 2.3. Standard: Performance Optimization **Do This:** Optimize application performance by: - Code Splitting: Implement code splitting to reduce initial load times. - Lazy Loading: Lazy load non-critical components and images. - Server-Side Rendering (SSR) or Static Site Generation (SSG): Use SSR or SSG for improved SEO and performance. Next.js (often used with Shadcn) supports these natively. - Caching: Implement caching at various levels (browser, CDN, server-side). - Image Optimization: Optimize images for web delivery (e.g., using "next/image"). **Don't Do This:** Ignore performance issues until they become critical. **Why:** Performance optimization improves user experience, reduces server load, and lowers infrastructure costs. **Example (Lazy Loading with Shadcn components integrated):** """jsx import React, { Suspense } from 'react'; import { Skeleton } from "@/components/ui/skeleton" const LazyComponent = React.lazy(() => import('./components/HeavyComponent')); function MyPage() { return ( <div> {/* Other content */} <Suspense fallback={<Skeleton className="w-[400px] h-[200px]" />}> <LazyComponent /> </Suspense> </div> ); } export default MyPage; """ **Anti-pattern:** Loading large JavaScript bundles on initial page load, causing slow initial rendering. ### 2.4. Standard: Security **Do This:** Implement security best practices, including: - Input Validation: Validate all user inputs to prevent injections. Use a library like Zod to guarantee your inputs are valid. - Authentication and Authorization: Secure authentication and authorization mechanisms. Use NextAuth.js or similar libraries. - HTTPS: Enforce HTTPS for all connections. - Regular Security Audits: Conduct regular security audits and penetration testing. - Dependency Updates: Keep dependencies up-to-date to patch known vulnerabilities. **Don't Do This:** Store sensitive information in client-side code or cookies. **Why:** Security is paramount to protect user data and prevent malicious attacks. **Example (Sanitzing text input):** """typescript import { z } from "zod" const schema = z.object({ title: z.string().min(3).max(255), body: z.string().min(10) }) function validate(input: unknown) { return schema.safeParse(input) } """ **Anti-pattern:** Exposing API keys or database credentials in client-side JavaScript or publicly accessible configuration files. ## 3. Shadcn-Specific Considerations ### 3.1. Standard: Component Versioning and Upgrades **Do This:** Keep Shadcn components up-to-date. Regularly check for updates and apply them using the "shadcn-ui" CLI. Test upgraded components thoroughly. **Don't Do This:** Use outdated versions of Shadcn components without applying security or bug fixes. **Why:** Upgrading Shadcn components ensures you benefit from the latest features, performance improvements, and security patches. **Example (Updating Shadcn components):** """bash npx shadcn-ui@latest update button """ ### 3.2. Standard: Customization and Theming **Do This:** Utilize Tailwind CSS variables and Shadcn's theming capabilities. Create reusable component variants using "class-variance-authority" (cva) to maintain consistency. **Don't Do This:** Directly modify the underlying CSS of Shadcn components without using Tailwind's utility classes or theming. **Why:** Leveraging Tailwind and Shadcn's theming ensures a consistent and maintainable design system. **Example (component variants using cva):** """typescript import { cva } from "class-variance-authority"; const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10", }, }, defaultVariants: { variant: "default", size: "default", }, } ); """ ### 3.3 Standard: Shadcn Component Overrides **Do This:** When overriding Shadcn components, ensure you are using CSS composition and Tailwind utility classes to maintain consistency with the base theme. Comment clearly when you override default Shadcn styles. **Don't Do This:** Use !important or overly specific CSS selectors to override styles as it introduces maintenance overhead. **Why**: Using proper CSS Composition and Tailwind utility classes helps maintain the themes and improves long-term maintainability of the codebase. """typescript /** * Override: Increasing the padding of the button to make it bigger. * You should document why a component is overridden. */ <Button className="px-8 py-4"> Click me </Button> """ ## 4. Modern Approaches and Patterns ### 4.1. Standard: Infrastructure as Code (IaC) **Do This:** Define and manage infrastructure using code (e.g., Terraform, AWS CloudFormation, or Pulumi, CDK). Automate infrastructure provisioning and configuration. **Don't Do This:** Manually provision and configure infrastructure resources. **Why:** IaC ensures consistent and repeatable infrastructure deployments, reduces manual errors, and enables version control for infrastructure changes. ### 4.2. Standard: Containerization **Do This:** Containerize applications using Docker. Use container orchestration platforms like Kubernetes or Docker Swarm for deployment and scaling. **Don't Do This:** Deploy applications directly to virtual machines without containerization. **Why:** Containerization provides isolation, portability, and scalability, simplifying deployment and management. ### 4.3. Standard: Serverless Functions **Do This:** Utilize serverless functions (e.g., AWS Lambda, Azure Functions, or Google Cloud Functions) for event-driven tasks and lightweight APIs. **Don't Do This:** Run all application logic in monolithic servers. **Why:** Serverless functions offer scalability, cost efficiency, and simplified operational management. ### 4.4 Standard: Edge Computing **Do This:** Consider leveraging edge computing platforms (e.g., Cloudflare Workers, Vercel Edge Functions, Netlify Edge Functions) to execute code closer to users for reduced latency. **Don't Do This:** Assume all processing must occur within the central application servers. **Why:** Edge computing enhances performance by minimizing network latency and improving geographically distributed user experiences. ## 5. Conclusion Adhering to these Deployment and DevOps standards will help ensure your Shadcn projects are reliable, scalable, secure, and maintainable. By following these guidelines, development teams can build high-quality applications that deliver exceptional user experiences. Regular reviews and updates to these standards are essential to keep pace with the evolving landscape of web development and the continuous improvements within the Shadcn ecosystem.
# Code Style and Conventions Standards for Shadcn This document outlines the code style and conventions to be followed when developing with Shadcn. Adhering to these standards ensures consistency, readability, maintainability, and performance across the entire codebase. These guidelines are designed to be used by developers and as context for AI coding assistants, promoting a unified approach to Shadcn development. ## 1. Formatting and General Style ### 1.1. Code Formatting * **Standard:** Use a consistent code formatter like Prettier. Configure it to enforce consistent indentation (2 spaces), line length (120 characters), and trailing commas. * **Why:** Consistent formatting improves readability and reduces unnecessary changes in diffs. * **Do This:** """javascript // .prettierrc.js module.exports = { semi: true, trailingComma: 'all', singleQuote: true, printWidth: 120, tabWidth: 2, useTabs: false, }; """ * **Don't Do This:** Manually format code inconsistently. Relying solely on developer preference leads to a cluttered codebase. * **Standard:** Integrate Prettier with ESLint (or similar linter) to automatically fix formatting issues. * **Why:** Automating formatting reduces cognitive load and ensures compliance. * **Do This:** """bash npm install --save-dev prettier eslint-plugin-prettier eslint-config-prettier """ """javascript // .eslintrc.js module.exports = { extends: [ 'eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'prettier', ], plugins: ['react', '@typescript-eslint', 'prettier'], rules: { 'prettier/prettier': 'error', // Add other ESLint rules here }, }; """ * **Don't Do This:** Skip linter integration. This forces developers to manually address style issues during code reviews. ### 1.2. File Structure * **Standard:** Organize components into directories based on functionality (e.g., "components/ui", "components/features"). Use the "components" directory as the root for all Shadcn UI components and custom components. * **Why:** Clear file structure enhances navigation and code discoverability. * **Do This:** """ src/ ├── components/ │ ├── ui/ │ │ ├── button.tsx │ │ ├── card.tsx │ │ └── ... │ ├── features/ │ │ ├── product-list.tsx │ │ ├── checkout-form.tsx │ │ └── ... │ └── layout/ │ ├── header.tsx │ └── footer.tsx ├── app/ │ ├── page.tsx │ └── ... └── lib/ ├── utils.ts └── ... """ * **Don't Do This:** Dump all components into a single directory, making it harder to find and manage. * **Standard:** Favor colocating related files (component, styles, tests) within the same directory. Also include stories for Storybook if using it. * **Why:** Easier to find and maintain related code. * **Do This:** """ src/components/ui/button/ ├── button.tsx ├── button.module.css //Or tailwind classes in the component ├── button.test.tsx └── button.stories.tsx """ * **Don't Do This:** Scatter related files across the project. ### 1.3. Imports and Exports * **Standard:** Use absolute imports for internal modules, with "@" as a root alias (configured in "tsconfig.json"). Use relative imports only for files within the same component directory. * **Why:** Prevents brittle import paths and improves code refactorability. * **Do This:** """typescript // tsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./src/*"] } } } """ """typescript // Correct Usage (absolute import) import { Button } from '@/components/ui/button'; // Correct Usage (relative import, within the same component directory) import styles from './button.module.css'; """ * **Don't Do This:** """typescript // Avoid: Relative imports from distant directories import { Button } from '../../../../components/ui/button'; """ * **Standard:** Group imports by origin (third-party libraries, internal modules, relative paths). Separate each group with a blank line. Order imports alphabetically within each group. * **Why:** Improves readability and makes it easier to identify dependencies. * **Do This:** """typescript import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { cn } from '@/lib/utils'; import { Input } from '@/components/ui/input'; import styles from './form.module.css'; """ * **Don't Do This:** Mix imports randomly; it obfuscates dependencies. ### 1.4. Comments and Documentation * **Standard:** Write clear, concise comments to explain complex logic, algorithms, or non-obvious code sections. Focus on *why* the code is written, not *what* it does. * **Why:** Clear documentation helps other developers (including your future self) understand the codebase. * **Do This:** """typescript /** * Calculates the total price of items in the cart, * applying any applicable discounts. * * @param {OrderItem[]} cartItems - An array of items in the cart. * @returns {number} The total price after discounts. */ function calculateTotalPrice(cartItems: OrderItem[]): number { // Apply a 10% discount for orders over $100. const basePrice = cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0); const discount = basePrice > 100 ? basePrice * 0.1 : 0; return basePrice - discount; } """ * **Don't Do This:** Over-comment obvious code, skip commenting complex parts. * **Standard:** Use JSDoc or TSDoc style comments for documenting components and functions. This allows tools like Storybook and documentation generators to automatically create API documentation. * **Why:** Enables automated API documentation and improves discoverability. * **Do This:** """typescript /** * A styled button component. * * @param {ButtonProps} props - The props for the button. * @returns {JSX.Element} A button element. */ export function Button({ children, ...props }: ButtonProps): JSX.Element { return ( <button {...props} className={cn("button", props.className)}> {children} </button> ); } """ * **Standard:** Keep documentation up-to-date with code changes. Outdated documentation is worse than no documentation. * **Why:** Ensures accuracy and relevance of documentation over time. ## 2. Naming Conventions ### 2.1. Variables and Constants * **Standard:** Use descriptive, camelCase names for variables and functions (e.g., "userName", "calculateTotalPrice"). * **Why:** Improves readability and clarity. * **Do This:** """typescript const userEmail = 'test@example.com'; function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) { // ... } """ * **Don't Do This:** Use short, cryptic names like "x", "y", or single-letter variable names. * **Standard:** Use SCREAMING_SNAKE_CASE for constants (e.g., "MAX_ITEMS", "API_URL"). * **Why:** Clearly distinguishes constants from variables. * **Do This:** """typescript const API_URL = 'https://api.example.com'; const MAX_RETRIES = 3; """ * **Don't Do This:** Use camelCase for constants. ### 2.2. Components * **Standard:** Use PascalCase for component names (e.g., "UserProfile", "ProductCard"). * **Why:** Consistent with React conventions and makes components easily identifiable. * **Do This:** """typescript function ProductCard(props: ProductCardProps): JSX.Element { // ... } """ * **Don't Do This:** Use camelCase or snake_case; it violates React component naming standards. * **Standard:** When creating UI components using Shadcn UI, stick to names related to the function it provides. Try to be as semantic as possible. Example : "LoginButton", "SideBarNavigation", "ProductCardImage" * **Standard:** Name component files the same as the component (e.g., "ProductCard.tsx" for the "ProductCard" component). * **Why:** Simplifies file lookup and improves project structure. * **Do This:** A "ProductCard" component should be placed in "ProductCard.tsx" file. * **Don't Do This:** Name files arbitrarily or inconsistently. ### 2.3. Hooks * **Standard:** Use "use" prefix for custom React hooks (e.g., "useUserData", "useFetch"). * **Why:** Follows React convention, making it clear that a function is a hook. * **Do This:** """typescript function useUserData(userId: string) { // ... } """ * **Don't Do This:** Omit the "use" prefix; it violates Hook naming standards set by React. ### 2.4. CSS Classes * **Standard:** Use a consistent naming convention for CSS classes, preferably BEM (Block, Element, Modifier) or a similar methodology. If using Tailwind CSS, leverage its utility-first approach directly in the component. * **Why:** Improves CSS maintainability and reduces naming conflicts. * **Do This (BEM):** """css .product-card { /* Block styles */ } .product-card__title { /* Element styles */ } .product-card--featured { /* Modifier styles */ } """ * **Do This (Tailwind CSS):** """tsx <div className="bg-white rounded-lg shadow-md p-4"> <h2 className="text-xl font-bold mb-2">Product Title</h2> <p className="text-gray-700">Product description...</p> </div> """ * **Don't Do This:** Use vague or generic class names without any methodology (e.g., "red", "small", "box"). ### 2.5 Type Names * **Standard:** Use PascalCase and the "Type" or "Interface" suffix when declare types for components like "ButtonType", "CardInterface". * **Why:** Improve readability of the code. * **Do This:** """typescript interface ButtonInterface{ variant: "default"|"destructive", size: "sm" | "lg", name: string } """ ## 3. Shadcn-Specific Conventions ### 3.1. Component Composition * **Standard:** Leverage Shadcn UI's composable components to build complex UIs. Avoid creating new low-level components if possible. * **Why:** Promotes consistency and reusability, and reduces code duplication. * **Do This:** """typescript import { Button } from '@/components/ui/button'; import { Card, CardHeader, CardBody } from '@/components/ui/card'; function UserProfileCard() { return ( <Card> <CardHeader>User Profile</CardHeader> <CardBody> <p>Name: John Doe</p> <Button>Edit Profile</Button> </CardBody> </Card> ); } """ * **Don't Do This:** Rebuild basic UI elements from scratch when Shadcn UI already provides them. ### 3.2. Customization with "cn" Utility * **Standard:** Use the "cn" utility (from "class-variance-authority") to easily combine Tailwind CSS classes and handle conditional class names. * **Why:** Simplifies class name management and improves readability. * **Do This:** """typescript import { cn } from '@/lib/utils'; interface ButtonProps { variant?: 'primary' | 'secondary'; size?: 'small' | 'large'; className?: string; children: React.ReactNode; } function Button({ variant = 'primary', size = 'small', className, children }: ButtonProps) { return ( <button className={cn( 'rounded-md font-semibold', { 'bg-blue-500 text-white': variant === 'primary', 'bg-gray-200 text-gray-700': variant === 'secondary', 'px-2 py-1 text-sm': size === 'small', 'px-4 py-2 text-base': size === 'large', }, className )} > {children} </button> ); } """ * **Don't Do This:** Manually concatenate class names with string interpolation; it becomes unwieldy and error-prone. ### 3.3. Overriding Styles * **Standard:** Use CSS variables or Tailwind CSS's customization features to override the default styles of Shadcn UI components. * **Why:** Preserves consistency and makes it easier to maintain a consistent design system. * **Do This (CSS Variables):** """css :root { --button-primary-bg: #007bff; } .button.primary { background-color: var(--button-primary-bg); } """ * **Do This (Tailwind CSS Customization):** In "tailwind.config.js": """javascript module.exports = { theme: { extend: { colors: { primary: '#007bff', secondary: '#6c757d', }, }, }, }; """ Then, in the component: """tsx <button className="bg-primary text-white font-bold py-2 px-4 rounded"> Click me </button> """ * **Don't Do This:** Use inline styles or overly-specific CSS selectors to override styles, as it reduces maintainability and increases the risk of conflicts. ### 3.4 Data Fetching and State Management * **Standard:** When fetching data for components, prefer using React Query or SWR. * **Why:** Handle data fetching and caching with a performatic way. * **Do This:** """tsx import { useQuery } from "@tanstack/react-query"; interface Props { id: string } function UserProfile({id}:Props) { const{isLoading, data:profile} = useQuery({ queryKey: ['profile',id], queryFn: ()=>fetch('/user/'+id).then(res=>res.json()) }) if(isLoading){ return <div>Loading...</div> } return <div>{profile?.name}</div> } """ ## 4. Modern Approaches and Patterns ### 4.1. Functional Components and Hooks * **Standard:** Prefer functional components and hooks over class components for new code. * **Why:** Functional components are simpler, more readable, and easier to test. Hooks promote code reuse and separation of concerns. * **Do This:** """typescript import { useState, useEffect } from 'react'; function Counter() { const [count, setCount] = useState(0); useEffect(() => { document.title = "Count: ${count}"; }, [count]); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ); } """ * **Don't Do This:** Use class components for new feature development without a specific reason. ### 4.2. TypeScript * **Standard:** Use TypeScript for all new code. Annotate components, functions, and variables with types. * **Why:** TypeScript improves code quality, reduces runtime errors, and enhances maintainability. * **Do This:** """typescript interface User { id: number; name: string; email: string; } function greetUser(user: User): string { return "Hello, ${user.name}!"; } """ * **Don't Do This:** Skip type annotations; it negates the benefits of TypeScript. ### 4.3 Utility Functions * **Standard:** Store utility functions in "lib/utils.ts" or similar dedicated files. * **Why:** Allow use the same function across all the project. * **Do This:** """typescript // lib/utils.ts import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } """ ## 5. Anti-Patterns to Avoid ### 5.1. Over-Engineering * **Anti-Pattern:** Creating overly complex solutions for simple problems. Avoid premature optimization or unnecessary abstraction. * **Why:** Increases code complexity and reduces maintainability. ### 5.2. Prop Drilling * **Anti-Pattern:** Passing props through multiple layers of components without being directly used. * **Why:** Makes code harder to understand and refactor. * **Solution:** Use context or state management libraries like Zustand or Redux to share data between components. ### 5.3. Mutating Props Directly * **Anti-Pattern:** Modifying props directly within a component. * **Why:** Violates React's unidirectional data flow and can lead to unpredictable behavior. * **Solution:** Use state to manage component-specific data and pass down event handlers to update the parent's state. ### 5.4. Inefficient Rendering * **Anti-Pattern:** Causing unnecessary re-renders by not memoizing components or using inefficient data structures. * **Why:** Impacts performance, especially in complex UIs. * **Solution:** Use "React.memo", "useMemo", and "useCallback" to optimize rendering. ### 5.5. Ignoring Accessibility * **Anti-Pattern:** Neglecting accessibility considerations (e.g., proper ARIA attributes, semantic HTML) when building UI components. * **Why:** Excludes users with disabilities and violates accessibility standards (WCAG). * **Solution:** Use semantic HTML, provide ARIA attributes where necessary, and test with assistive technologies. ## 6. Security Best Practices ### 6.1. Input Validation * **Standard:** Always validate user inputs on both the client and server sides. * **Why:** Prevents security vulnerabilities such as XSS and SQL injection. * **Do This:** """typescript // Client-side validation function validateEmail(email: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } // Server-side validation (using a library like Zod) import { z } from 'zod'; const userSchema = z.object({ email: z.string().email(), password: z.string().min(8), }); function createUser(data: any) { const validatedData = userSchema.parse(data); // ... } """ ### 6.2. Secure Authentication * **Standard:** Use industry-standard authentication and authorization mechanisms such as OAuth 2.0 and JWT. * **Why:** Protects user accounts and data. ### 6.3. Data Sanitization * **Standard:** Sanitize data before rendering it in the UI to prevent XSS attacks. * **Why:** Prevents malicious scripts from executing in the user's browser. * **Do This:** """typescript function escapeHtml(unsafe: string) { return unsafe .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function renderUserInput(userInput: string) { return <div>{escapeHtml(userInput)}</div>; } """ ### 6.4 Dependency Management * **Standard:** Regularly update dependencies to patch security vulnerabilities. * **Why:** Mitigates risks associated with outdated libraries. * **Do This:** Use "npm audit" or "yarn audit" to identify and fix vulnerabilities. ## 7. Testing ### 7.1. Unit Testing * **Standard:** Write unit tests for individual components and utility functions. * **Why:** Ensures code correctness and facilitates refactoring. * **Do This:** """typescript // button.test.tsx import { render, screen } from '@testing-library/react'; import { Button } from './button'; test('renders button with correct text', () => { render(<Button>Click me</Button>); const buttonElement = screen.getByText('Click me'); expect(buttonElement).toBeInTheDocument(); }); """ ### 7.2. Integration Testing * **Standard:** Write integration tests to verify the interaction between components. * **Why:** Ensures that components work together correctly. ### 7.3. End-to-End Testing * **Standard:** Use end-to-end testing frameworks to simulate user interactions and verify the overall application functionality. * **Why:** Validates the entire user flow and catches integration issues. * **Tools:** Cypress, Playwright ## 8. Performance Optimization ### 8.1. Code Splitting * **Standard:** Implement code splitting to reduce the initial load time by loading only the necessary code. * **Why:** Improves application performance, resulting in better user experience, especially with large applications. * **Do This:** * Lazy load with "React.lazy" and "<Suspense>". * Dynamic import statements. ### 8.2. Memoization * **Standard:** Use "React.memo" for functional components that receive the same props repeatedly. Use also "useMemo" and "useCallback" hooks if needed. * **Why:** Avoids unnecessary re-renders. ### 8.3. Image Optimization * **Standard:** Optimize all images by compressing them, using appropriate formats (WebP), and lazy loading them. * **Why:** Reduces bandwidth consumption and improves page load times. * **Do This:** Use the "<Image>" component provided by Next.js. By following these code style and conventions, development teams can create high-quality, maintainable, and performant Shadcn applications. This guide can also be used to configure AI coding assistants for automatic code formatting and style enforcement.
# API Integration Standards for Shadcn This document outlines the standards for integrating APIs with Shadcn-based applications. It covers patterns for connecting with backend services and external APIs, with a focus on maintainability, performance, and security. These standards leverage modern approaches and patterns compatible with the latest versions of Shadcn, React, and related libraries. ## 1. Architecture and Principles ### 1.1. Separation of Concerns **Do This:** * Separate API interaction logic from UI components. Use dedicated services or hooks to handle API calls. **Don't Do This:** * Make direct API calls within Shadcn components. This tightly couples the UI to the backend, making testing and maintenance difficult. **Why**: * *Maintainability*: Decoupled code is easier to understand, test, and modify. * *Reusability*: API logic can be reused across multiple components. * *Testability*: API interactions can be mocked and tested independently. **Example:** """typescript // api/productService.ts import { api } from "./api"; export const getProducts = async () => { try { const response = await api.get("/products"); return response.data; } catch (error) { console.error("Error fetching products:", error); throw error; // Re-throw to allow components to handle the error } }; // components/ProductList.tsx import { useEffect, useState } from "react"; import { getProducts } from "@/api/productService"; // Assuming "@/api" is aliased in your tsconfig.json import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" function ProductList() { const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchProducts = async () => { try { const data = await getProducts(); setProducts(data); setLoading(false); } catch (err: any) { setError(err); setLoading(false); } }; fetchProducts(); }, []); if (loading) { return <div>Loading products...</div>; } if (error) { return <div>Error: {error.message}</div>; } return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> {products.map((product) => ( <Card key={product.id}> <CardHeader> <CardTitle>{product.name}</CardTitle> </CardHeader> <CardContent> {product.description} </CardContent> </Card> ))} </div> ); } export default ProductList; """ ### 1.2. Data Fetching Strategies **Do This**: * Use React Query, SWR, or similar libraries for data fetching, caching, and state management. **Don't Do This**: * Rely solely on "useEffect" for API calls and storing data in local component state without any caching strategy. **Why**: * *Performance*: Caching reduces unnecessary API calls. * *User Experience*: Optimistic updates and background data fetching improve responsiveness. * *Simplified State Management*: Reduces boilerplate code for handling loading, error, and data states. **Example (using React Query):** """typescript // api/productService.ts - same as above // components/ProductList.tsx import { useQuery } from "@tanstack/react-query"; import { getProducts } from "@/api/productService"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" function ProductList() { const { data: products, isLoading, error } = useQuery({ queryKey: ["products"], queryFn: getProducts, }); if (isLoading) { return <div>Loading products...</div>; } if (error) { return <div>Error: {error.message}</div>; } return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> {products?.map((product) => ( // Safe navigation operator <Card key={product.id}> <CardHeader> <CardTitle>{product.name}</CardTitle> </CardHeader> <CardContent> {product.description} </CardContent> </Card> ))} </div> ); } export default ProductList; """ ### 1.3. Error Handling **Do This:** * Implement robust error handling at both the API service level and the component level. * Use try-catch blocks for API calls and provide user-friendly error messages. **Don't Do This:** * Ignore errors or display generic, unhelpful error messages to the user. **Why**: * *User Experience*: Inform users about issues and provide guidance. * *Debugging*: Detailed error messages aid in identifying and resolving problems. * *Resilience*: Gracefully handle unexpected errors to prevent application crashes. **Example**: """typescript // api/productService.ts import { api } from "./api"; export const getProducts = async () => { try { const response = await api.get("/products"); return response.data; } catch (error: any) { console.error("Error fetching products:", error); //Custom handling based on error type if (error.response && error.response.status === 404) { throw new Error("Products not found."); }else{ throw new Error("Failed to fetch products."); //Generic error } } }; // components/ProductList.tsx import { useQuery } from "@tanstack/react-query"; import { getProducts } from "@/api/productService"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" import { AlertCircle } from "lucide-react" function ProductList() { const { data: products, isLoading, error } = useQuery({ queryKey: ["products"], queryFn: getProducts, }); if (isLoading) { return <div>Loading products...</div>; } if (error) { return ( <Alert variant="destructive"> <AlertCircle className="h-4 w-4" /> <AlertTitle>Error</AlertTitle> <AlertDescription> {error.message} </AlertDescription> </Alert> ); } return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> {products?.map((product) => ( <Card key={product.id}> <CardHeader> <CardTitle>{product.name}</CardTitle> </CardHeader> <CardContent> {product.description} </CardContent> </Card> ))} </div> ); } export default ProductList; """ ### 1.4 API Client Setup **Do This:** * Utilize Axios or Fetch API with appropriate headers and base URLs configured. * Implement interceptors for request/response processing (e.g., adding authentication tokens, handling errors). **Don't Do This:** * Use a direct fetch call without error handling or central configuration **Why:** * *Centralized Configuration:* easier to manage base URLs and headers * *Interceptors:* Interceptors provide a mechanism for intercepting and modifying HTTP requests and responses. **Example (Axios):** """typescript // api/apiClient.ts import axios from 'axios'; const apiClient = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, headers: { 'Content-Type': 'application/json', }, }); apiClient.interceptors.request.use( (config) => { // Retrieve token from localStorage, cookie, or context const token = localStorage.getItem('authToken'); if (token) { config.headers.Authorization = "Bearer ${token}"; } return config; }, (error) => { return Promise.reject(error); } ); apiClient.interceptors.response.use( (response) => { return response; }, (error) => { // Handle errors, e.g., redirect to login on 401 Unauthorized if (error.response && error.response.status === 401) { // Redirect to login page or handle token refresh window.location.href = '/login'; } return Promise.reject(error); } ); export default apiClient; """ ## 2. Implementation Details ### 2.1. Data Transformation **Do This:** * Transform API responses into a format suitable for the UI. This could involve renaming properties, converting data types, or structuring data for specific components. **Don't Do This:** * Pass raw API responses directly to Shadcn components. Components should receive only the data they need, in the format they expect. **Why**: * *Decoupling*: Protects components from changes in the API response structure. * *Performance*: Reduces the amount of data processed by components. * *Clarity*: Makes component props more predictable and self-documenting. **Example:** """typescript // api/productService.ts import { api } from "./api"; export const getProducts = async () => { try { const response = await api.get("/products"); //Data transformation const transformedProducts = response.data.map((product: any) => ({ id: product.product_id, name: product.product_name, description: product.product_description, price: product.product_price, imageUrl: product.product_image_url, //rename image URL })); return transformedProducts; } catch (error: any) { console.error("Error fetching products:", error); //Custom handling based on error type if (error.response && error.response.status === 404) { throw new Error("Products not found."); }else{ throw new Error("Failed to fetch products."); //Generic error } } }; """ ### 2.2. Optimistic Updates **Do This:** * Use optimistic updates to provide a more responsive user experience. Optimistically update the UI before the API request completes, and revert the change if the request fails. This is commonly done with React Query's "useMutation". **Don't Do This:** * Wait for the API request to complete before updating the UI, especially for actions like creating or updating data. **Why**: * *User Experience*: Makes the application feel faster and more responsive. * *Perceived Performance*: Immediate feedback improves user satisfaction. **Example (using React Query):** """typescript import { useMutation, useQueryClient } from "@tanstack/react-query"; import { createProduct } from "@/api/productService"; // Assuming this function handles the POST request function ProductForm() { const queryClient = useQueryClient(); const mutation = useMutation({ mutationFn: createProduct, onSuccess: () => { // Invalidate the products query to refetch the data queryClient.invalidateQueries({ queryKey: ["products"] }); }, }); const handleSubmit = async (data: any) => { mutation.mutate(data); }; return ( <form onSubmit={handleSubmit}> {/* Form fields */} <button type="submit" disabled={mutation.isLoading}> {mutation.isLoading ? "Creating..." : "Create Product"} </button> {mutation.isError && <div>Error: {mutation.error?.message}</div>} </form> ); } """ ### 2.3. Handling Loading States **Do This:** * Display loading indicators (e.g., spinners, progress bars) while waiting for API responses. **Don't Do This:** * Leave the UI unresponsive or display empty data without indicating that data is being loaded. **Why**: * *User Experience*: Informs users that the application is working and prevents confusion. * *Clarity*: Provides visual feedback about the application's state. **Example (with Shadcn components):** """typescript // components/ProductList.tsx import { useQuery } from "@tanstack/react-query"; import { getProducts } from "@/api/productService"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton" function ProductList() { const { data: products, isLoading, error } = useQuery({ queryKey: ["products"], queryFn: getProducts, }); if (isLoading) { return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> {Array.from({ length: 3 }).map((_, index) => ( <Card key={index}> <CardHeader> <CardTitle><Skeleton className="h-4 w-[80%]" /></CardTitle> </CardHeader> <CardContent> <Skeleton className="h-4 w-[60%]" /> <Skeleton className="h-4 w-[40%]" /> </CardContent> </Card> ))} </div> ); } if (error) { return <div>Error: {error.message}</div>; } return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> {products?.map((product) => ( <Card key={product.id}> <CardHeader> <CardTitle>{product.name}</CardTitle> </CardHeader> <CardContent> {product.description} </CardContent> </Card> ))} </div> ); } export default ProductList; """ ### 2.4. Data Validation **Do This:** * Validate both incoming (request) and outgoing (response) data to ensure data integrity. Libraries like Zod or Yup can be used for schema validation. **Don't Do This:** * Trust that API responses are always in the expected format. **Why**: * *Data Integrity*: Prevents invalid data from being stored or displayed. * *Error Prevention*: Catches errors early, before they cause unexpected behavior. * *Security*: Helps prevent injection attacks and other vulnerabilities. **Example (using Zod):** """typescript import { z } from "zod"; const productSchema = z.object({ id: z.number(), name: z.string(), description: z.string(), price: z.number(), imageUrl: z.string().url(), }); type Product = z.infer<typeof productSchema>; // api/productService.ts import { api } from "./api"; export const getProducts = async () => { try { const response = await api.get("/products"); // Validate each item in the array const validatedProducts = z.array(productSchema).parse(response.data); return validatedProducts; } catch (error: any) { console.error("Error fetching products:", error); if (error instanceof z.ZodError) { console.error("Zod validation error:", error.errors); throw new Error("Invalid product data received from the server."); } else if (error.response && error.response.status === 404) { throw new Error("Products not found."); } else { throw new Error("Failed to fetch products."); } } }; """ ### 2.5. Pagination **Do This** Implement server-side pagination for large datasets. Display pagination controls in the UI (e.g., page numbers, next/previous buttons). **Don't Do This** Load an entire dataset into memory at once. **Why** * *Performance*: Reduces the amount of data transferred and processed. * *Scalability*: Enables the application to handle larger datasets. * *User Experience*: Improves page load times. **Example (React Query + Shadcn):** """typescript // api/productService.ts import { api } from "./api"; export const getProducts = async (page: number, limit: number = 10) => { try { const response = await api.get("/products?_page=${page}&_limit=${limit}"); return { data: response.data, totalCount: parseInt(response.headers['x-total-count'] || '0', 10) // Use x-total-count or similar header }; } catch (error: any) { console.error("Error fetching products:", error); throw new Error("Failed to fetch products."); //Generic error } }; // components/ProductList.tsx import { useQuery } from "@tanstack/react-query"; import { getProducts } from "@/api/productService"; import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Button } from "@/components/ui/button" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { useState } from "react" function ProductList() { const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); const { data, isLoading, error } = useQuery({ queryKey: ["products", page, pageSize], queryFn: () => getProducts(page, pageSize), keepPreviousData: true, //Ensures that the previous data remains visible while the new data is being fetched }); const totalCount = data?.totalCount || 0; const pageCount = Math.ceil(totalCount / pageSize); const handleNextPage = () => { setPage(prev => Math.min(prev + 1, pageCount)); }; const handlePrevPage = () => { setPage(prev => Math.max(prev - 1, 1)); }; const handlePageSizeChange = (size: number) => { setPageSize(size); setPage(1); // Reset to the first page when page size changes }; if (isLoading) { return <div>Loading products...</div>; } if (error) { return <div>Error: {error.message}</div>; } return ( <div> <Table> <TableCaption>A list of your products.</TableCaption> <TableHeader> <TableRow> <TableHead className="w-[100px]">ID</TableHead> <TableHead>Name</TableHead> <TableHead>Description</TableHead> </TableRow> </TableHeader> <TableBody> {data?.data.map((product: any) => ( <TableRow key={product.id}> <TableCell className="font-medium">{product.id}</TableCell> <TableCell>{product.name}</TableCell> <TableCell>{product.description}</TableCell> </TableRow> ))} </TableBody> </Table> <div className="flex justify-between items-center mt-4"> <span>Total Products: {totalCount}</span> <div className="flex items-center space-x-2"> <Select value={pageSize.toString()} onValueChange={(value) => handlePageSizeChange(parseInt(value))}> <SelectTrigger className="w-[180px]"> <SelectValue placeholder="Select page size" /> </SelectTrigger> <SelectContent> <SelectItem value="5">5 per page</SelectItem> <SelectItem value="10">10 per page</SelectItem> <SelectItem value="20">20 per page</SelectItem> </SelectContent> </Select> <Button variant="outline" size="sm" onClick={handlePrevPage} disabled={page === 1}> Previous </Button> <Button variant="outline" size="sm" onClick={handleNextPage} disabled={page === pageCount}> Next </Button> </div> </div> </div> ); } export default ProductList; """ ## 3. Security Considerations ### 3.1. Authentication and Authorization **Do This:** * Implement secure authentication and authorization mechanisms to protect APIs. Use industry-standard protocols like OAuth 2.0 or JWT. * Store API keys and secrets securely (e.g., using environment variables or a secrets management service). * Never commit API keys or secrets directly to the codebase. **Don't Do This:** * Use weak or insecure authentication methods. * Expose API keys or secrets in client-side code. **Why**: * *Data Protection*: Prevents unauthorized access to sensitive data. * *Security*: Reduces the risk of security breaches. * *Compliance*: Adheres to security and privacy regulations. **Example (using JWT for authentication):** """typescript // api/api.ts import axios from 'axios'; const api = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, headers: { 'Content-Type': 'application/json', }, }); api.interceptors.request.use( (config) => { const token = localStorage.getItem('jwt'); if (token) { config.headers.Authorization = "Bearer ${token}"; } return config; }, (error) => { return Promise.reject(error); } ); export default api; """ ### 3.2. Input Validation **Do This:** * Validate all user inputs on both the client-side and the server-side to prevent injection attacks and other vulnerabilities. * Use appropriate data types and formats for input fields. * Sanitize user inputs before sending them to the API. **Don't Do This:** * Trust that user inputs are always valid or safe. * Allow users to submit arbitrary code or commands to the API. **Why**: * *Security*: Prevents malicious code from being injected into the application. * *Data Integrity*: Ensures that data is stored in the correct format. * *Reliability*: Prevents unexpected errors caused by invalid data. ### 3.3. Rate Limiting **Do This:** * Implement rate limiting to prevent abuse and protect APIs from being overwhelmed by excessive requests. **Don't Do This:** * Allow unlimited requests from a single user or IP address. **Why**: * *Availability*: Protects APIs from denial-of-service attacks. * *Scalability*: Ensures that APIs can handle a large number of requests. * *Fair Usage*: Prevents abuse of API resources. ## 4. Modern Approaches and Patterns ### 4.1. Server Actions (Next.js 13+) **Do This:** * Utilize Server Actions for server-side data mutations directly from components. This simplifies data handling by eliminating the need for a separate API layer for simple create/update/delete operations. **Don't Do This:** * Overuse Server Actions for complex logic, keep such actions atomic and straightforward. **Why**: * *Simplicity*: Reduces the amount of code needed for data mutations. * *Performance*: Executes data mutations on the server, reducing client-side overhead. * *Security*: Runs server-side code, with no API endpoints being exposed. **Example:** """typescript // app/actions.ts 'use server'; // Mark this file as a server action import { revalidatePath } from 'next/cache'; //Use to revalidate the data export async function createProduct(formData: FormData) { const name = formData.get('name') as string; const description = formData.get('description') as string; // Perform server-side logic here (e.g., database interaction) try { // Simulate database interaction const newProduct = { id: Date.now(), name, description, }; //Revalidate the product list to show new product revalidatePath('/products'); return { success: true, data: newProduct }; } catch (error) { console.error('Error creating product:', error); return { success: false, error: 'Failed to create product' }; } } // app/products/page.tsx import { createProduct } from '../actions'; export default async function ProductsPage() { return ( <form action={createProduct}> <input type="text" name="name" placeholder="Product Name" required/> <input type="text" name="description" placeholder="Description" required/> <button type="submit">Create Product</button> </form> ); } """ ### 4.2. tRPC **Do This:** * Consider using tRPC for building type-safe APIs between your Next.js frontend and backend. This provides end-to-end type safety without requiring code generation steps. **Don't Do This:** * Use tRPC for very large, complex APIs where a more traditional REST or GraphQL approach might be more appropriate. **Why**: * *Type Safety*: Ensures type safety across the entire application. * *Developer Experience*: Simplifies API development with automatic type inference. * *Performance*: Minimizes data serialization and deserialization overhead. This document provides a comprehensive set of guidelines for API integration in Shadcn projects. By adhering to these standards, development teams can ensure that their applications are maintainable, performant, secure, and provide a great user experience.
# Security Best Practices Standards for Shadcn This document outlines the security best practices for developing applications using Shadcn, aiming to help developers build secure, resilient, and maintainable code. These guidelines are essential for protecting against common web vulnerabilities and ensuring the confidentiality, integrity, and availability of your applications. ## 1. Input Validation and Sanitization ### 1.1 The Importance of Input Validation **Why:** Input validation is the cornerstone of web application security. Malicious users can inject harmful data into your application through numerous input fields, leading to vulnerabilities like SQL Injection, Cross-Site Scripting (XSS), and Command Injection. **Do This:** * **Always validate all user inputs:** This includes form fields, URL parameters, cookies, headers, and data from external APIs. * **Use a whitelist approach:** Define acceptable input patterns and reject anything that doesn't match. * **Validate on both client-side and server-side:** Client-side validation enhances user experience, while server-side validation is critical for security. **Don't Do This:** * **Never trust user input implicitly:** Regardless of the source, treat all input as potentially malicious. * **Rely solely on client-side validation:** Bypassing client-side checks is trivial. **Example:** """typescript // Server-side validation with Zod (recommended) import { z } from 'zod'; const userSchema = z.object({ username: z.string().min(3).max(50), email: z.string().email(), age: z.number().min(18).max(120), }); export async function createUser(data: any) { try { const validatedData = userSchema.parse(data); // Process validatedData to your database console.log("validatedData : " , validatedData); return { success: true, data: validatedData }; } catch (error:any) { console.error("Validation Error:", error.errors); return { success: false, error: error.errors }; } } //Usage : const userData = { username: 'john_doe', email: 'john@example.com', age: 25 }; createUser(userData); """ ### 1.2 Sanitizing Output and Data Escaping **Why:** Sanitizing output and escaping data prevents XSS attacks by ensuring that user-provided content is rendered as text, not as executable code. **Do This:** * **Use appropriate escaping mechanisms:** When displaying user input, use the correct escaping methods for the target context (HTML, JavaScript, CSS, URLs, XML, etc.). * **Context-aware encoding:** Choose encoding schemes suitable for where the data will be used. **Don't Do This:** * **Concatenate user input directly into HTML or JavaScript:** Doing so creates opportunities for XSS vulnerabilities. * **Disable escaping features:** Understand the implications and have a robust alternative. **Example:** """tsx //Preventing XSS in React/Shadcn import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { useState } from "react"; function DisplayName({ name }: { name: string }) { const [displayName, setDisplayName] = useState(name) return ( <Card> <CardHeader> <CardTitle>Safe Display of User Name</CardTitle> </CardHeader> <CardContent> <div>Hello, {displayName} !</div> </CardContent> </Card> ); } export default DisplayName; //Usage (Assuming your UI is Typescript-based Shadcn) <DisplayName name={"<script>alert('XSS')</script>"} /> //React escapes by default, correctly rendering the tags as text. //Use "dangerouslySetInnerHTML" with EXTREME CAUTION, only when sanitizing the string """ ### 1.3 Common Anti-Patterns * Ignoring input validation for authenticated users. * Using overly permissive regular expressions for validation. * Trusting hidden form fields and client-side logic. ## 2. Authentication and Authorization ### 2.1 Secure Authentication Practices **Why:** Robust authentication is vital to verify the identity of users accessing your application. Weak authentication schemes can be easily compromised. **Do This:** * **Use strong password policies:** Enforce minimum length, complexity requirements, and encourage password managers. * **Implement multi-factor authentication (MFA):** MFA adds an extra layer of security beyond passwords. * **Use established authentication libraries:** Leverage trusted libraries like NextAuth.js for authentication and session management. * **Rate limit login attempts:** Protect against brute-force attacks. **Don't Do This:** * **Store passwords in plain text:** Always hash passwords using a strong hashing algorithm like bcrypt or Argon2. * **Use predictable session IDs:** Generate cryptographically secure session IDs. * **Implement custom authentication schemes without security expertise:** Employ well-vetted solutions. **Example:** """typescript // Using NextAuth.js for authentication import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { verifyPassword } from "@/lib/auth"; import { getUserByEmail } from "@/lib/db"; export const authOptions = { session: { strategy: "jwt", }, providers: [ CredentialsProvider({ async authorize(credentials) { const email = credentials?.email; const password = credentials?.password; if (!email || !password) { return null; } const user = await getUserByEmail(email); if (!user || !user?.password) { return null; } const isValid = await verifyPassword(password, user.password); if (!isValid) { return null; } return { email: user.email, name: user.name, image: user.image, }; }, }), ], secret: process.env.NEXTAUTH_SECRET, }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST }; """ ### 2.2 Authorization and Access Control **Why:** Authorization defines what authenticated users are allowed to do within your application. Inadequate access controls can lead to privilege escalation attacks. **Do This:** * **Implement role-based access control (RBAC):** Assign permissions based on user roles. * **Use least privilege principle:** Grant users only the minimum necessary permissions to perform their tasks. * **Validate authorization checks on the server-side:** Don't rely solely on client-side checks. * **Centralize authorization logic:** Manage permissions in a consistent and maintainable way. **Don't Do This:** * **Expose sensitive data or functionality without authorization checks.** * **Hardcode user IDs or roles in code.** * **Assume that authenticated users are authorized to access all resources.** **Example:** """typescript //Role-based authorization middleware import { NextRequest, NextResponse } from 'next/server'; import { getSession } from 'next-auth/react'; export async function middleware(req: NextRequest) { const session = await getSession({req}) const url = req.nextUrl.pathname if (!session && url === '/admin') { return NextResponse.redirect(new URL('/login', req.url)) } return NextResponse.next() } export const config = { matcher: ['/admin/:path*'] } """ ### 2.3 Common Anti-Patterns * Defaulting to administrator privileges for all users. * Failing to invalidate sessions after password changes or logout. * Using weak or obsolete cryptographic algorithms. ## 3. Data Protection ### 3.1 Encryption of Sensitive Data **Why:** Encryption protects sensitive data both in transit and at rest. Even if attackers gain access to your database, encrypted data remains confidential. **Do This:** * **Encrypt sensitive data in transit:** Use HTTPS to encrypt all communications between the client and server. * **Encrypt sensitive data at rest:** Use encryption libraries and cloud provider's encryption services to encrypt data stored in databases, file systems, and backups. * **Implement key management practices:** Securely store and manage encryption keys. Utilize Hardware Security Modules (HSMs) or Key Management Services (KMS). * **Rotate keys regularly:** Rotate encryption keys periodically to reduce the impact of potential key compromises. **Don't Do This:** * **Store sensitive data in plain text without encryption.** * **Use weak or outdated encryption algorithms.** * **Hardcode encryption keys in the application code.** **Example:** """typescript //Encryption using a library like 'crypto-js' for demonstration import CryptoJS from 'crypto-js'; const secretKey = process.env.ENCRYPTION_KEY; // Store securely! export function encryptData(data: string): string { if (!secretKey) { throw new Error('Encryption key is not defined.'); } return CryptoJS.AES.encrypt(data, secretKey).toString(); } export function decryptData(encryptedData: string): string { if (!secretKey) { throw new Error('Encryption key is not defined.'); } const bytes = CryptoJS.AES.decrypt(encryptedData, secretKey); return bytes.toString(CryptoJS.enc.Utf8); } """ ### 3.2 Data Minimization and Retention **Why:** Reducing the amount of sensitive data you collect and store minimizes the potential damage from data breaches. **Do This:** * **Collect only necessary data:** Only collect data that is essential for the intended purpose. * **Implement data retention policies:** Define how long data should be stored and securely delete it when no longer needed. * **Anonymize or pseudonymize data:** When possible, remove identifying information or replace it with pseudonyms. **Don't Do This:** * **Collect and store excessive amounts of personal data without a clear purpose.** * **Retain data indefinitely without a retention policy.** * **Fail to securely dispose of data that is no longer needed.** ### 3.3 Common Anti-Patterns * Storing API keys or secrets in public repositories or client-side code. * Failing to implement proper backup and disaster recovery procedures. * Ignoring data privacy regulations (e.g., GDPR, CCPA). ## 4. Dependencies and Third-Party Libraries ### 4.1 Dependency Management **Why:** Third-party libraries and dependencies introduce potential vulnerabilities. Staying up-to-date with patched versions is essential. **Do This:** * **Use a package manager:** Use npm, yarn, or pnpm to manage dependencies. * **Regularly update dependencies:** Use "npm update", "yarn upgrade", or "pnpm update" to keep dependencies updated. * **Automated vulnerability scanning:** Integrate tools like Snyk or Dependabot into your CI/CD pipeline to automatically detect and remediate vulnerabilities. * **Pin exact dependency versions:** Use exact versioning in your "package.json" to avoid unexpected breaking changes. * **Review dependencies:** Understand the dependencies you are using and their potential security risks. **Don't Do This:** * **Use outdated dependencies with known vulnerabilities.** * **Install dependencies from untrusted sources.** * **Ignore security alerts from dependency scanning tools.** **Example:** """json // package.json { "dependencies": { "react": "18.2.0", "zod": "3.22.4", "@radix-ui/react-slot": "1.0.2" }, "devDependencies": { "eslint": "8.56.0", "typescript": "5.3.3" } } """ ### 4.2 Third-Party Component Auditing **Why:** Shadcn UI relies heavily on components; understanding their data handling is critical. **Do This:** * **Review the code of Shadcn UI components:** Familiarize yourself with how data is handled and processed within components. * **Check Radix UI for known vulnerabilities:** Radix UI forms the base of many Shadcn UI components, so stay informed about its security status. * **Follow Shadcn's update recommendations:** Incorporate updates promptly to benefit from security patches and improvements. **Don't Do This:** * **Blindly integrate components without understanding their potential impact on security.** * **Ignore the underlying risks associated with Radix UI.** ### 4.3 Common Anti-Patterns * Using vulnerable or unmaintained third-party libraries. * Ignoring license restrictions on third-party code. * Exposing third-party configuration details. ## 5. Error Handling and Logging ### 5.1 Secure Error Handling **Why:** Error messages can reveal sensitive information about your application. **Do This:** * **Implement generic error messages for users:** Avoid exposing internal details or stack traces. * **Log detailed error information on the server-side:** Log errors to a secure location for debugging and monitoring. * **Use structured logging:** Use a structured logging format (e.g., JSON) to facilitate analysis. * **Handle exceptions gracefully:** Prevent unhandled exceptions from crashing the application or exposing sensitive data. **Don't Do This:** * **Display detailed error messages directly to users.** * **Log sensitive data in error messages.** * **Ignore or suppress errors without proper handling.** **Example:** """typescript // Centralized error handling async function fetchData() { try { // Attempt to fetch data const response = await fetch('https://api.example.com/data'); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } const data = await response.json(); return data; } catch (error: any) { // Log the full error details on the server console.error('Error fetching data:', error); // Return a generic error message to the client. return { error: 'Failed to retrieve data. Please try again later.' }; } } """ ### 5.2 Comprehensive Logging Practices **Why:** Logging provides valuable insights for security monitoring, incident response, and auditing. **Do This:** * **Log important events:** Log authentication attempts, authorization failures, data access, and modifications. * **Include relevant context:** Include timestamps, user IDs, IP addresses, and other relevant information in log entries. * **Secure log storage:** Store logs in a secure location with restricted access. * **Regularly review logs:** Analyze logs for suspicious activity or security incidents. * **Centralized log management:** Use a centralized logging system to collect and analyze logs from multiple sources. **Don't Do This:** * **Log sensitive data in plain text.** * **Store logs on the same server as the application.** * **Fail to monitor logs for security incidents.** ### 5.3 Common Anti-Patterns * Logging too much or too little information. * Writing logs directly to files without proper security controls. * Failing to rotate or archive logs. ## 6. Deployment and Infrastructure Security ### 6.1 Secure Configuration Management **Why:** Vulnerable configurations can expose your application to attacks. **Do This:** * **Use environment variables for configuration:** Store sensitive configuration settings (e.g., API keys, database passwords) in environment variables. * **Automated configuration management:** Use tools like Ansible, Chef, or Puppet to automate configuration management. * **Principle of least privilege for infrastructure access:** Grant only necessary access rights for each component and user. * **Regularly review your environment:** Check environment variables and related configurations (API Keys, database credentials) to prevent leaks. * **Monitor configuration changes:** Track configuration changes to identify and mitigate potential security issues. **Don't Do This:** * **Hardcode configuration settings in code.** * **Store configuration files in public repositories.** * **Grant excessive permissions to infrastructure components.** **Example:** """bash #Example of setting and accessing secure environment variables #On the server : (using .env file) DATABASE_URL=your_db_url API_KEY=your_api_key NEXTAUTH_SECRET=your_nextauth_secret #Accessing it in Next.js/Typescript const apiKey = process.env.API_KEY; if (!apiKey) { console.error('API key is missing from environment variables.'); // Handle the missing API key scenario (e.g., throw an error or use a default value) } """ ### 6.2 Network Security **Why:** Protecting your network infrastructure is crucial to prevent unauthorized access. **Do This:** * **Use firewalls to restrict network traffic:** Configure firewalls to allow only necessary traffic. * **Segment your network:** Isolate different parts of your application (e.g., web server, database server) into separate network segments. * **Regular security audits:** Conduct routine security reviews to identify vulnerabilities. * **Intrusion detection:** Implement intrusion detection systems to monitor for malicious. **Don't Do This:** * **Expose unnecessary ports or services to the internet.** * **Use default passwords for network devices.** * **Fail to monitor network traffic for suspicious activity.** ### 6.3 Common Anti-Patterns * Using default or weak passwords for infrastructure components. * Failing to patch security vulnerabilities in operating systems and software. * Exposing sensitive services to the public internet without proper protection. ## 7. Regular Security Testing and Auditing **Why:** Proactive security testing and auditing can identify vulnerabilities before attackers exploit them. **Do This:** * **Code Reviews:** Perform regular manual code reviews with a focus on security issues. * **Static Analysis:** Use linters, IDE plugins, and static analysis tools to automatically find common security flaws. * **Dynamic Analysis:** Perform dynamic analysis or "fuzzing" to test the application's response to malformed or unexpected inputs. * **Penetration Testing:** Hire security professionals to perform penetration testing – simulating real-world attacks to find weaknesses. * **Security Audits:** Conduct periodic security audits to assess overall security posture. Remember that security is an ongoing process, not a one-time fix. By following these best practices, you can build more secure and resilient Shadcn UI applications.
# State Management Standards for Shadcn This document outlines the standards for managing application state in Shadcn projects. It covers various approaches, best practices, and common pitfalls to avoid, focusing on maintainability, performance, and a consistent development experience. This complements the component-level coding standard for Shadcn. ## 1. Choosing a State Management Solution Shadcn provides a robust foundation for building UIs, but it does not prescribe a specific state management library. The choice of state management solution depends on the complexity of your application. ### 1.1 Standard: Use Context API + "useReducer" for simple state management * **Do This:** Start with the built-in React Context API combined with the "useReducer" hook for simple state management needs within isolated parts of your application. * **Don't Do This:** Immediately reach for a large state management library (like Redux or Zustand) for simple applications. This adds unnecessary complexity. **Why:** React's built-in Context API is sufficient for managing state in isolated components or smaller applications, providing a lightweight and straightforward solution without introducing external dependencies. The "useReducer" hook is a better alternative of "useState" hook for state management of more complex components. **Example:** """jsx // ThemeContext.jsx import React, { createContext, useReducer, useContext } from 'react'; const ThemeContext = createContext(); const initialState = { darkMode: false, }; const reducer = (state, action) => { switch (action.type) { case 'TOGGLE_DARK_MODE': return { ...state, darkMode: !state.darkMode }; default: return state; } }; const ThemeProvider = ({ children }) => { const [state, dispatch] = useReducer(reducer, initialState); return ( <ThemeContext.Provider value={{ state, dispatch }}> {children} </ThemeContext.Provider> ); }; const useTheme = () => { const context = useContext(ThemeContext); if (context === undefined) { throw new Error("useTheme must be used within a ThemeProvider"); } return context; }; export { ThemeProvider, useTheme }; """ """jsx // ComponentUsingTheme.jsx import React from 'react'; import { useTheme } from './ThemeContext'; import { Button } from "@/components/ui/button" const ComponentUsingTheme = () => { const { state, dispatch } = useTheme(); return ( <div> <p>Dark Mode: {state.darkMode ? 'On' : 'Off'}</p> <Button onClick={() => dispatch({ type: 'TOGGLE_DARK_MODE' })}> Toggle Dark Mode </Button> </div> ); }; export default ComponentUsingTheme; """ ### 1.2 Standard: Use Zustand for moderate complexity * **Do This:** Consider Zustand for global state management in medium-sized applications for its simplicity and minimal boilerplate. * **Don't Do This:** Use Redux or similar verbose state management libraries when Zustand can achieve the same results with less code. **Why:** Zustand offers a simple and unopinionated approach to state management. It's easy to learn and integrate, making it ideal for managing global application state without the complexity of larger libraries. **Example:** """jsx // store.js import { create } from 'zustand'; const useStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), reset: () => set({ count: 0 }), })); export default useStore; """ """jsx // CounterComponent.jsx import React from 'react'; import useStore from './store'; import { Button } from "@/components/ui/button" const CounterComponent = () => { const count = useStore((state) => state.count); const increment = useStore((state) => state.increment); const decrement = useStore((state) => state.decrement); const reset = useStore((state) => state.reset) return ( <div> <p>Count: {count}</p> <Button onClick={increment}>Increment</Button> <Button onClick={decrement}>Decrement</Button> <Button onClick={reset}>Reset</Button> </div> ); }; export default CounterComponent; """ ### 1.3 Standard: Use Redux Toolkit **only** for complex global application state * **Do This:** Reserve Redux Toolkit for very complex applications that benefit from its structured approach, middleware, and debugging tools. Only use Redux with Redux Toolkit. * **Don't Do This:** Use Redux without Redux Toolkit. Redux without RTK is too verbose and difficult to manage. **Why:** Redux, especially with Redux Toolkit, provides a predictable state container that's beneficial for large, complex applications. It includes features like middleware for handling asynchronous actions, and tools for debugging and time-traveling state. **Example:** """jsx // store.js import { configureStore } from '@reduxjs/toolkit'; import counterReducer from './counterSlice'; export const store = configureStore({ reducer: { counter: counterReducer, }, }); """ """jsx // 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; }, incrementByAmount: (state, action) => { state.value += action.payload; }, }, }); export const { increment, decrement, incrementByAmount } = counterSlice.actions; export default counterSlice.reducer; """ """jsx // CounterComponent.jsx import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { increment, decrement, incrementByAmount } from './counterSlice'; import { Button } from "@/components/ui/button" const CounterComponent = () => { 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> ); }; export default CounterComponent; """ ### 1.4 Standard: Use React Query/Tanstack Query for server state management * **Do This:** Use React Query (now known as TanStack Query) for managing server state, caching, and background updates. * **Don't Do This:** Use local state or Redux to cache server data manually. React Query is specifically designed for this purpose and handles complexities automatically. **Why:** React Query simplifies fetching, caching, synchronizing, and updating server state. It offers built-in features for caching, background updates, retries, and more, reducing boilerplate and improving performance. **Example:** """jsx // usePosts.js import { useQuery } from '@tanstack/react-query'; const fetchPosts = async () => { const response = await fetch('/api/posts'); if (!response.ok) { throw new Error('Failed to fetch posts'); } return response.json(); }; const usePosts = () => { return useQuery({ queryKey: ['posts'], queryFn: fetchPosts }); }; export default usePosts; """ """jsx // PostsComponent.jsx import React from 'react'; import usePosts from './usePosts'; const PostsComponent = () => { const { data: posts, isLoading, error } = usePosts(); if (isLoading) return <p>Loading...</p>; if (error) return <p>Error: {error.message}</p>; return ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); }; export default PostsComponent; """ ## 2. Data Flow and Reactivity ### 2.1 Standard: Prefer unidirectional data flow. * **Do This:** Ensure data flows in one direction, typically from parent components to children via props, and updates flow back to the parent through callbacks or state management solutions. Avoid two-way data binding. * **Don't Do This:** Mutate props directly in child components. This makes debugging harder and leads to unpredictable behavior. **Why:** Unidirectional data flow increases predictability, makes debugging easier, and simplifies reasoning about application state changes. **Example (Correct):** """jsx // ParentComponent.jsx import React, { useState } from 'react'; import ChildComponent from './ChildComponent'; const ParentComponent = () => { const [message, setMessage] = useState('Hello'); const handleMessageChange = (newMessage) => { setMessage(newMessage); }; return ( <div> <p>Parent Message: {message}</p> <ChildComponent message={message} onMessageChange={handleMessageChange} /> </div> ); }; export default ParentComponent; """ """jsx // ChildComponent.jsx import React from 'react'; import { Input } from "@/components/ui/input" const ChildComponent = ({ message, onMessageChange }) => { const handleChange = (event) => { onMessageChange(event.target.value); }; return ( <div> <Input type="text" value={message} onChange={handleChange} /> </div> ); }; export default ChildComponent; """ **Example (Incorrect - Prop Mutation):** """jsx // IncorrectChildComponent.jsx import React from 'react'; import { Input } from "@/components/ui/input" const IncorrectChildComponent = ({ message }) => { const handleChange = (event) => { //DO NOT DO THIS message = event.target.value; // Directly mutating prop }; return ( <div> <Input type="text" value={message} onChange={handleChange} /> </div> ); }; export default IncorrectChildComponent; """ ### 2.2 Standard: Utilize keys for dynamic lists. * **Do This:** Provide unique "key" props when rendering dynamic lists of elements. The key be a stable and predictable value related to THAT ITEM (e.g. item id). * **Don't Do This:** Use array indices as keys, especially if the order of the list items might change. Also don't use randomly generated UUIDS because those change on every render. **Why:** Keys help React efficiently update the DOM when items are added, removed, or reordered. Using incorrect keys (or no keys) degrades performance and can cause unexpected behavior. **Example:** """jsx // Correct const items = [ { id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }, ]; const ItemList = () => { return ( <ul> {items.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> ); }; export default ItemList; """ """jsx // Incorrect const items = [ { id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }, ]; const BadItemList = () => { return ( <ul> {items.map((item, index) => ( <li key={index}>{item.name}</li> ))} </ul> ); }; export default BadItemList; """ ### 2.3 Standard: Optimize Re-renders with "React.memo" * **Do This:** Use "React.memo" to prevent unnecessary re-renders of components when their props haven't changed. Shadcn provides visually polished components; avoiding unnecessary re-renders on key UI elements is paramount. * **Don't Do This:** Wrap every component with "React.memo" without profiling first. This can add overhead if the component re-renders infrequently or if the prop comparison is expensive. **Why:** "React.memo" optimizes performance by skipping re-renders for components when the props haven't changed. **Example:** """jsx import React from 'react'; const MyComponent = ({ data, onClick }) => { console.log('MyComponent re-rendered'); return ( <button onClick={onClick}> {data.name} </button> ); }; export default React.memo(MyComponent, (prevProps, nextProps) => { // Custom comparison function (optional) // Return true if props are equal, false if props are different return prevProps.data.id === nextProps.data.id; }); """ Without the second argument (the equality function), "React.memo" will do a shallow comparison of the props. If complex props are used (like objects), consider providing the custom equality function for a precise comparison. ### 2.4 Standard: Use "useCallback" and "useMemo" Hooks * **Do This:** Utilize "useCallback" and "useMemo" hooks for optimizing performance by memoizing functions and values that are passed as props to children components. * **Don't Do This:** Overuse "useCallback" and "useMemo" without careful profiling. It is a common mistake to wrap every possible function/value. Also do not forget the dependency arrays of these functions. **Why:** "useCallback" and "useMemo" prevent recreating functions and values on every render. This improves performance by ensuring that child components only re-render when their props have actually changed. **Example:** """jsx import React, { useState, useCallback, useMemo } from 'react'; import MyComponent from './MyComponent'; const ParentComponent = () => { const [count, setCount] = useState(0); // Memoize the data object const data = useMemo(() => ({ id: 1, name: 'Item' }), []); // Memoize the onClick function const handleClick = useCallback(() => { setCount(count + 1); }, [count]); return ( <div> <p>Count: {count}</p> <MyComponent data={data} onClick={handleClick} /> <button onClick={() => setCount(count + 1)}>Increment Parent</button> </div> ); }; export default ParentComponent; """ In this setup, "handleClick" is only recreated when "count" changes, and "data" is only created once preventing unnecessary renders of "MyComponent". ## 3. Shadcn Specific State Management Considerations ### 3.1 Standard: Manage component-level styling state within the component. * **Do This:** For simple UI interactions like toggling a component's visibility or changing its appearance, manage the state directly within the component using "useState". **Why:** This keeps the component self-contained and reduces complexity. Avoid unnecessary state management overhead for simple UI interactions. **Example:** """jsx import React, { useState } from 'react'; import { Button } from "@/components/ui/button" const ToggleComponent = () => { const [isVisible, setIsVisible] = useState(false); return ( <div> <Button onClick={() => setIsVisible(!isVisible)}> {isVisible ? 'Hide' : 'Show'} </Button> {isVisible && <p>This content is visible.</p>} </div> ); }; export default ToggleComponent; """ ### 3.2 Standard: Use Zod for schema validation of form data * **Do This:** Use Zod for validating forms that leverage Shadcn component. Zod has first class Typescript support. This ensures data accuracy and consistency, improving the user experience and the reliability of the application. * **Don't Do This:** Write custom validation functions or regular expressions without leveraging a validation library, as it can be error-prone and hard to maintain. **Why:** Zod offers a declarative and type-safe way to define schemas and validate data. It integrates well with TypeScript, providing excellent developer experience. **Example:** """jsx import { z } from "zod" const formSchema = z.object({ username: z.string().min(2, { message: "Username must be at least 2 characters.", }), email: z.string().email({ message: "Invalid email address.", }), }) export default formSchema """ ### 3.3 Standard: Use "useTransition" for Optimistic UI Updates. * **Do This:** When performing actions that update server state, use the "useTransition" hook to provide an optimistic UI update. This makes the UI feel more responsive. * **Don't Do This:** Block the UI while waiting for server responses which creates a slow user experience. **Why:** Optimistic UI updates provide immediate feedback to the user, improving the perceived performance and responsiveness of the application. **Example:** """jsx import React, { useState, useTransition } from 'react'; import { Button } from "@/components/ui/button" const LikeButton = ({ postId }) => { const [isLiked, setIsLiked] = useState(false); const [isPending, startTransition] = useTransition(); const handleClick = () => { startTransition(() => { setIsLiked(!isLiked); // Simulate an API call setTimeout(() => { console.log("API call completed"); }, 500); }); }; return ( <Button disabled={isPending} onClick={handleClick}> {isLiked ? 'Unlike' : 'Like'} {isPending ? 'Updating...' : ''} </Button> ); }; export default LikeButton; """ ## 4. Anti-Patterns to Avoid ### 4.1 Anti-Pattern: Prop Drilling * **Avoid This:** Passing props through multiple layers of components when only a deeply nested component needs the data. This can hinder component reusability and makes the code hard to maintain. **Solution:** Use Context API, Zustand, or Redux to provide the data where it’s needed without passing it through intermediate components. ### 4.2 Anti-Pattern: Over-reliance on Global State * **Avoid This:** Storing everything in global state. Managing component-specific state locally can lead to unnecessary re-renders throughout the application. **Solution:** Identify which state truly needs to be global and which can be managed locally within components or using Context API for a specific component tree. ### 4.3 Anti-Pattern: Ignoring React Query Optimizations * **Avoid This:** Neglecting React Query's features like "staleTime", "cacheTime", and "refetchOnWindowFocus". **Solution:** Configure React Query's options based on your application's needs. For frequently updated data, a shorter "staleTime" is appropriate. For less frequently updated data, a longer "staleTime" can reduce unnecessary API calls. Consider disabling "refetchOnWindowFocus" if updates are not needed when the user switches back to the tab. ### 4.4 Anti-Pattern: Mutating State Directly * **Avoid This:** Directly modifying state variables without using state update functions (e.g., "useState"'s "setState", Redux reducers, Zustand's "set"). **Solution:** Always use the proper state update mechanisms to trigger re-renders and maintain predictable state changes. ## 5. Community Standards and Patterns ### 5.1 Standard: Atomic State Updates * **Do This:** When updating state that depends on the previous state, use the functional form of "setState" in "useState" or the updater function in "useReducer" or Zustand. **Why:** This ensures that you are working with the correct previous state, avoiding potential race conditions or stale data issues, especially when dealing with asynchronous updates. **Example:** """jsx // useState with functional update import React, { useState } from 'react'; import { Button } from "@/components/ui/button" const CounterComponent = () => { const [count, setCount] = useState(0); const increment = () => { setCount(prevCount => prevCount + 1); }; return ( <div> <p>Count: {count}</p> <Button onClick={increment}>Increment</Button> </div> ); }; export default CounterComponent; """ ### 5.2 Standard: Colocation of State Logic * **Do This:** Keep state logic close to where it's used. If a component's state logic becomes complex, consider moving it into a custom hook. **Why:** This improves code organization, testability, and maintainability. **Example:** """jsx // useCounter.js import { useState } from 'react'; const useCounter = (initialValue = 0) => { const [count, setCount] = useState(initialValue); const increment = () => { setCount(prevCount => prevCount + 1); }; const decrement = () => { setCount(prevCount => prevCount - 1); }; return { count, increment, decrement }; }; export default useCounter; """ """jsx // CounterComponent.jsx import React from 'react'; import useCounter from './useCounter'; import { Button } from "@/components/ui/button" const CounterComponent = () => { const { count, increment, decrement } = useCounter(10); return ( <div> <p>Count: {count}</p> <Button onClick={increment}>Increment</Button> <Button onClick={decrement}>Decrement</Button> </div> ); }; export default CounterComponent; """ ### 5.3 Standard: Graceful Degradation/Error Handling * **Do This:** When fetching data, especially with React Query, handle loading and error states gracefully. Display informative messages or fallback content to provide a good user experience even when things go wrong. * **Don't Do This:** Display blank screens or unhandled errors to the user. **Why:** Proper error handling makes your application more robust and user-friendly. **Example:** """jsx import React from 'react'; import usePosts from './usePosts'; const PostsComponent = () => { const { data: posts, isLoading, error } = usePosts(); if (isLoading) return <p>Loading posts...</p>; if (error) return <p>Error fetching posts: {error.message}</p>; return ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); }; export default PostsComponent; """ ## 6. Performance Optimization Techniques ### 6.1 Standard: Code Splitting * **Do This:** Implement code splitting using dynamic imports ("React.lazy" and "Suspense") to reduce the initial load time of your application. Defer loading non-critical components. **Why:** Code splitting improves performance by loading only the code that is needed for the current view, thus reducing the initial bundle size. **Example:** """jsx import React, { Suspense } from 'react'; const LazyComponent = React.lazy(() => import('./HeavyComponent')); const MyComponent = () => { return ( <div> <Suspense fallback={<div>Loading...</div>}> <LazyComponent /> </Suspense> </div> ); }; export default MyComponent; """ ### 6.2 Standard: Virtualization of large lists * **Do This:** Use libraries like "react-window" or "react-virtualized" to efficiently render large lists of data. **Why:** Virtualization renders only the visible items in the list, significantly improving performance for long lists. **Example:** """jsx import React from 'react'; import { FixedSizeList as List } from 'react-window'; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); const VirtualizedList = () => { return ( <List height={150} itemCount={1000} itemSize={35} width={300} > {Row} </List> ); }; export default VirtualizedList; """ ### 6.3 Standard: Debouncing and Throttling * **Do This:** Use debouncing and throttling techniques to limit the rate at which functions are executed, particularly for event handlers that fire rapidly (e.g., "onChange" on an input field). * **Don't Do This:** Execute expensive operations on every event. **Why:** Debouncing and throttling improve performance by reducing the number of function calls, which is especially important for optimizing user input handling and API calls. **Example:** """jsx import React, { useState, useCallback } from 'react'; import { debounce } from 'lodash'; import { Input } from "@/components/ui/input" const SearchComponent = () => { const [searchTerm, setSearchTerm] = useState(''); const handleSearch = (value) => { console.log('Searching for:', value); // Perform API call or other expensive operation here }; const debouncedSearch = useCallback( debounce((value) => { handleSearch(value); }, 300), [] ); const handleChange = (event) => { const { value } = event.target; setSearchTerm(value); debouncedSearch(value); }; return ( <div> <Input type="text" placeholder="Search..." value={searchTerm} onChange={handleChange} /> </div> ); }; export default SearchComponent; """ ## 7. Security Considerations for State Management ### 7.1 Standard: Limit State Exposure * **Do This:** Avoid storing sensitive information (e.g., passwords, API keys, personal data) directly in client-side state if it's not absolutely necessary. If you must store sensitive data temporarily, encrypt it and clear it from the state as soon as it's no longer needed. * **Don't Do This:** Store sensitive data in plaintext in local storage or cookies. **Why:** Minimizing the exposure of sensitive data reduces the risk of it being compromised through XSS attacks or other vulnerabilities. ### 7.2 Standard: Sanitize User Inputs * **Do This:** Sanitize and validate all user inputs before storing them in the state. This helps prevent XSS attacks and other vulnerabilities. * **Don't Do This:** Directly use user inputs without any form of validation or sanitization which could be a security risk. **Why:** Sanitizing user inputs ensures that malicious scripts or code cannot be injected into your application through user-controlled data. ### 7.3 Standard: Secure API Communication * **Do This:** Use HTTPS for all API communication to encrypt data in transit. Implement proper authentication and authorization mechanisms to ensure that only authorized users can access and modify state. Use secure cookies with the "HttpOnly" and "Secure" flags. * **Don't Do This:** Use HTTP for API communication. **Why:** HTTPS encrypts the data exchanged between the client and the server, protecting it from eavesdropping and tampering. Proper authentication and authorization ensure that only authorized users can modify application state. ### 7. 4 Standard: Prevent State Injection * **Do This:** Protect against state injection attacks by ensuring that only authorized code can modify the application's state. Use immutable data structures to prevent unauthorized modifications of state. * **Don't Do This:** Allow external scripts or unauthorized code to directly modify the application state. **Why:** Protecting against unauthorized state modifications can prevent malicious actors from manipulating the state, leading to unexpected behavior or data breaches. This updated document provides a comprehensive guide to state management in Shadcn projects, covering various approaches, best practices, and security considerations. Adhering to these guidelines will result in more maintainable, performant, and secure applications.